ТРИЗ, Haskell и функциональное мышление

в 18:21, , рубрики: haskell, рекурсия, ТРИЗ, функциональное программирование

При слове ТРИЗ, часто вспоминают тезис "идеальная система — та, которой нет (а ее функция при этом выполняется)". Как хороший админ, который не появляется в офисе, а все при этом исправно работает.

Функция и система — критически важные понятия в ТРИЗ, говорят даже о функциональном стиле мышления. Правда при этих словах лично у меня сразу возникает ассоциация с функциональными языками программирования.

Попробуем посмотреть, насколько органично идеи функционального мышления ТРИЗ отображаются на Haskell, одном чистых функциональных языков общего назначения.

ТРИЗ, Haskell и функциональное мышление - 1

Функция

Функция — модель изменения свойства объекта функции ("изделия") носителем функции ("инструментом").

Инструмент — то, с помощью чего мы совершаем некую работу, т.е. что-то меняем. Как правило именно его требуется усовершенствовать или создать. Соответственно именно носитель функции обычно подразумевается под словом "система" во всех ТРИЗовских рассуждениях про оную.

Изделие — то, что мы изменяем (обрабатываем) при помощи инструмента.

ТРИЗ, Haskell и функциональное мышление - 2

Главная функция — потребительское свойство, ради удовлетворения которого создается техническая система.

Сама функция задается обычно простым глаголом, отражающим суть процесса (не специальным термином, чтобы не мешала инерция мышления), в формулировку включают носитель и объект функции.

Например: молоток перемещает гвоздь; метла перемещает мусор; кружка удерживает кофе; пылесос перемещает пыль; топливо перемещает ракету.

Рассмотрим, в частности, чашку с кофе.
Чашка удерживает кофе.
Носитель функции (инструмент) — чашка, объект функции — кофе, функция — удерживать.

ТРИЗ, Haskell и функциональное мышление - 3

-- Задаем типы входных данных и результата выполнения
-- Предположим, типы данных Чашки и Кофе уже где-то заданы
hold :: Cup -> Coffee -> Coffee   

-- вызываем функцию hold - "удержать" с параметрами в виде конкретных чашки и кофе
cup `hold` coffee                  

Функция hold должна быть полиморфна, поскольку чашка может удерживать не только кофе и кофе можно налить не только в чашку:

-- На входе пара сущностей типа а и b, на выходе измененная сущность типа b
hold :: a -> b -> b

-- что будет, если налить кофе в термос
thermos `hold` coffee

-- что будет, если пролить кофе на рубашку
shirt `hold` coffee 

Инструмент и изделие могут изменяться, а суть их взаимодействия, выраженная функцией, остается прежней. По статистике большинство парных функций между элементами технических систем можно описать тремя десятками глаголов (перемещать, удерживать, нагревать, поглощать, информировать и пр.). Каждый из них с точки зрения Haskell-реализации представляет собой полиморфную функцию. Как, впрочем, и остальные глаголы естественного языка.

Обратная функция

В реальном мире всегда есть и обратная функция — действие изделия на инструмент (третий закон Ньютона никто не отменял).

ТРИЗ, Haskell и функциональное мышление - 4

Например, обрабатываемый металл затупляет сверло, нерадивый ученик утомляет преподавателя, файл уменьшает свободное место на диске.
В примере с кофе он нагревает и пачкает чашку.

ТРИЗ, Haskell и функциональное мышление - 5

Как правило обратная функция нам вредит (износ инструмента, доп.затраты), однако в иных ситуациях мы можем извлечь из нее пользу. Например, погреть руки о теплую чашку, будучи в прохладном помещении.

hold:: a -> b -> b
warm :: a -> b -> b

cup `hold` coffee
coffee `warm` cup

Цепочки функций

В случае, когда число элементов системы больше двух (т.е., фактически, всегда) их рассматривают попарно, получая цепочки функций.

Например, на рисунке ниже рука несет (перемещает) поднос с чашкой, поднос удерживает чашку, чашка удерживает кофе.

ТРИЗ, Haskell и функциональное мышление - 6

((arm `move` wrist) `hold` cup) `hold` coffee

Избавимся от скобок, задав левую ассоциативность

infixl 9 hold

arm `move` wrist `hold` cup `hold` coffee

Запись на Haskell очень близка к записи на естественном языке.

И цепочка в обратную сторону: кофе нагревает чашку, чашка нагревает блюдце, блюдце нагружает руку.

infixl 9 warm, weight

coffee `warm` cup `warm` wrist `weight` arm

Понимание главной функции и взаимодействий между элементами позволяет оптимально выбрать границы системы, включить лишь то, что нужно для выполнения целевой задачи (ничего не упустив) и не усложнять модель сверх необходимого.

Система, которой нет ...

Нам нужна функция, точнее результат ее применения, а инструмент сам по себе не нужен. Это расходный материал, привлекаемый лишь в силу необходимости.

Удерживать кофе может джезва, чайник, термос, блюдце и стол и рубашка (если кофе неосторожно пролили).

Мы даже были бы не против, если кофе САМ себя удерживал. Как это происходит, например, с водой в невесомости на космической станции.

ТРИЗ, Haskell и функциональное мышление - 7

Однако на зацикленных формулировках вроде "кофе удерживает кофе" в ТРИЗ останавливаться не принято, поскольку она бесполезна с практической точки зрения — не дает информации об элементах, за счет которых достигается результат.

С точки зрения программирования такая рекурсивная формулировка плоха тем, что здесь нет условия окончания рекурсии.

Нужно уйти в глубину и указать какие именно части (подсистемы) обеспечивают выполнение функции.

Жидкость принимает в невесомости компактную форму за счет сил поверхностного натяжения. Т.о. более подходящим описанием ситуации будет: поверхностный слой удерживает внутренний объем кофе.

Можно представить весь объем кофе как матрешку слоев, каждый из которых удерживает друг друга. При этом основную работу делает внешний слой.

ТРИЗ, Haskell и функциональное мышление - 8

-- Пусть первый слой - поверхностный, пятый - самый глубокий
let coffee = [layer1, layer2, layer3, layer4, layer5]

head coffee `hold` tail coffee

Впрочем, если нам важно влияние слоев друг на друга (например, в плане поглощения света),
можно изобрести велосипед и описать последовательные взаимодействия явно.

Рекурсивная природа явления сохраниться, но за счет понимания взаимосвязей подсистем мы сможем задать условия выхода из рекурсии и обратить ее себе на службу.

ТРИЗ, Haskell и функциональное мышление - 9

-- Функция "удерживать", которая выводит свою полную формулировку
hold :: String -> String -> String
hold tool "" = tool
hold tool workpiece = tool ++ " -> holds -> " ++ workpiece

-- Функция "удерживать себя". 
-- Принимает на вход нарезанный слоями объект и 
-- последовательно вызывает обычную функцию "удержать" 
-- для каждого слоя и оствшегося объема
selfHold :: [String] -> String
selfHold [] = ""
selfHold (x:xs) = x `hold` selfHold xs

-- запускаем самоудержание трехслойного объекта
selfHold ["Layer1","Layer2","Layer3"]

в итоге получим

Layer1 -> holds -> Layer2 -> holds -> Layer3

Рекурсивность реализации никуда не исчезла, но стала конструктивной, а не бессмысленно зацикленной.

Тоже самое можно записать и короче, через свертку списка:

foldl1 hold ["Layer1","Layer2","Layer3"]

Заключение

Видение технической системы как ткани из функций, связывающих ее во едино и определяющих суть и предназначение, чрезвычайно роднит ТРИЗ с функциональными языками программирования, в которых функция — основная управляющая конструкция.

Рассмотренный подход служит хорошим подспорьем в плане декомпозиции задачи и управления сложностью модели.

Автор: shbma

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js