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

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

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

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

zccvii9scnn-q2qcmygdhj5bzgg.png


Функция

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

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

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

o7gi89p6_cl4b1lf4tyjd9jyzj8.png

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

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

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

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

eujnjuvlzefgtlfrdqq-ccv-y6w.png

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

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

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

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

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

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

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


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

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

zkmubuloa9mw2ppabxsfsphjtek.png

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

kormxcrqrk6tfbix5nkeeqoy2i0.png

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

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

cup `hold` coffee
coffee `warm` cup


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

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

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

qbjze7pw_g2bim2hmers65l_qae.png

((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

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


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

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

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

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

vdumu0pqgib8zw6etvl_ywzk-2a.gif

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

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

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

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

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

3aeasoebqz_gmbcjy9y_qf_fuv8.png

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

head coffee `hold` tail coffee

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

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

f32cl-t9qmjjnm7wkdfsiwpnlvo.png

-- Функция "удерживать", которая выводит свою полную формулировку
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"]


Заключение

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

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

© Habrahabr.ru