Хабрахабр

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

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

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

Попробуем посмотреть, насколько органично идеи функционального мышления ТРИЗ отображаются на 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"]

Заключение

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

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

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»