Pull to refresh

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

Reading time4 min
Views12K

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


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


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



Функция


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


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


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



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


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


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


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



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

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

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


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

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

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

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


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


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



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



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


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

cup `hold` coffee
coffee `warm` cup

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


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


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



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

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


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


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


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


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



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


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


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


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


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



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

head coffee `hold` tail coffee

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


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



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

Заключение


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


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

Tags:
Hubs:
Total votes 24: ↑16 and ↓8+8
Comments27

Articles