Что означает композиционность в контексте функционального программирования?
что имеют в виду функциональные программисты, когда они говорят, что определенная вещь является композиционной или не композиционной?
некоторые из утверждений такого рода, которые я читал:
- структуры управления не сочетаются.
- потоки не составляют.
- монадические операции являются составными.
7 ответов:
Марсело Кантос дал довольно хорошее объяснение, но я думаю, что это может быть немного более точным.
тип вещи является композиционным, когда несколько экземпляров могут быть объединены определенным образом для получения одного и того же типа вещи.
структура управления компоновкой. такие языки, как C, делают различие между выражения, который может быть составлен с использованием операторов для получения новых выражений, и заявления, который может быть составлен с использованием структур управления, таких как
if,forи "структура управления последовательностью", которая просто выполняет инструкции по порядку. Дело в том, что эти две категории не находятся на равных основаниях - многие структуры управления используют выражения (например, выражение, оцененноеifчтобы выбрать, какую ветвь выполнить), но выражения не могут использовать структуры управления (например, вы не можете вернутьforпетля.) Хотя это может показаться безумным или бессмысленным хотят "вернутьforцикл", на самом деле общая идея обработки управляющих структур как первоклассных объектов, которые могут быть сохранены и переданы, не только возможна, но и полезна. В ленивых функциональных языках, таких как Haskell, структуры управления, такие какifиforмогут быть представлены как обычные функции, которыми можно манипулировать в выражениях так же, как и любым другим термином, позволяя такие вещи, как функции, которые "строят" другие функции согласно параметрам они передаются, и возвращают их вызывающему объекту. Поэтому, хотя C (например) делит "то, что программист может захотеть сделать" на две категории и ограничивает способы объединения объектов из этих категорий, Haskell (например) имеет только одну категорию и не накладывает таких ограничений, поэтому в этом смысле он обеспечивает большую композиционность.компонуемость нить. я предполагаю, как Марсело Кантос сделал, что вы действительно говорите о композиционности потоков, использующих блокировки / мьютексы. Это немного сложнее, потому что на первый взгляд мы можете есть потоки, которые используют несколько блокировок; но важно то, что мы не можем иметь потоки, которые используют несколько блокировок С ГАРАНТИЯМИ, что они предназначены, чтобы иметь.
мы можем определить замок как тип вещи, которая имеет определенные операции, которые могут быть выполнены на нем, которые приходят с определенными гарантиями. Одна гарантия is: предположим, что есть объект блокировки
x, то при условии, что каждый процесс, который называетlock(x)в конце концов звонитunlock(x)любой вызоваlock(x)в конечном итоге успешно вернуться сxзаблокировано текущим потоком / процессом. Такая гарантия значительно упрощает рассуждения о поведении программы.к сожалению, если есть больше чем один замок в мире, это уже не правда. Если поток A вызывает
lock(x); lock(y);и поток B вызываетlock(y); lock(x);тогда возможно, что захватывает замокxи B захватывает замокyи они оба будут бесконечно ждать, пока другой поток освободит другую блокировку: тупик. Таким образом, блокировки не могут быть составными, потому что когда вы используете более одного, вы не можете просто утверждать, что эта важная гарантия все еще имеет -- не без анализа кода, чтобы увидеть, как он управляет замками. Другими словами, вы больше не можете позволить себе рассматривать функции как "черные ящики".вещи, которые являются составными хороши, потому что они позволяют абстракции, что означает, что они позволяют нам рассуждать о коде, не заботясь обо всех деталях, и это уменьшает когнитивную нагрузку на программиста.
простым примером композиционности является командная строка Linux, где символ трубы позволяет комбинировать простые команды (ls, grep, cat и т. д.) практически неограниченным количеством способов, тем самым" составляя " большое количество сложных поведений из небольшого количества более простых примитивов.
есть несколько преимуществ для композиционности:
- более равномерное поведение: в качестве примера, имея одну команду, которая implements " показать результаты на одной странице время" (
more) вы получаете степень единообразие подкачки, которое не было бы возможно, если бы каждая команда была реализовать свои собственные механизмы (и флаги командной строки)для выполнения подкачки.- меньше повторной работы по внедрению (Сухой): вместо того, чтобы иметь судью различные реализации подкачки, есть только один, который используется повсюду.
- больше функциональности для данной суммы усилия по осуществлению: существующие примитивы могут быть объединены, чтобы решить гораздо больший круг задач, чем что? если бы те же усилия пошел в реализацию монолитные, несоставные команды.
Как показывает пример командной строки Linux, композиционность не обязательно ограничивается функциональным программированием, но концепция одна и та же: иметь небольшие фрагменты кода, которые выполняют ограниченные задачи, и создавать более сложные функции, соответствующим образом маршрутизируя выходы и входы.
дело в том, что функциональное программирование хорошо подходит для этого: с неизменяемые переменные и ограничения на побочные эффекты вы можете составить более легко, поскольку вам не нужно беспокоиться о том, что происходит под капотом в вызываемой функции - например, обновление общей переменной, чтобы результат был недействительным для определенных последовательностей операций, или доступ к общей блокировке, чтобы определенные последовательности вызовов давали тупик.
это функциональное программирование composability-любая функция зависит только от ее входных параметров, а выход может быть передан любому функция, которая может обрабатывать тип возвращаемого значения.
по расширению, имея меньше типов данных дает больше композиционности. Богатые Хики в Clojure сказал что-то вроде
каждый новый тип объектов, по своей сути совместимы со всеми кодами написано
что, безусловно, хорошо сделано.
практическая композиционность также зависит от стандартизации на небольшом наборе типов данных, таких как Unix команды выполняют со своим стандартом "текст на основе строки с разделителями табуляции".
Постскриптум
Эрик Реймонд написал книгу о философии Unix, два из перечисленных им принципов проектирования были
- правило модульности: напишите простые части соединенные чистыми интерфейсами.
- правило композиции: разработка программ для подключения к другим программам.
From http://catb.org/~esr / writings / taoup / html / ch01s06.html#id2877537
композиция в информатике-это способность собирать сложное поведение путем агрегирования более простого поведения. Примером этого является функциональная декомпозиция, при которой комплексная функция разбивается на более мелкие легко усваиваемые функции и собирается в конечную систему с помощью функции верхнего уровня. Можно сказать, что функция верхнего уровня "сложила" части в целое.
некоторые понятия не так легко составить. Например, потокобезопасная структура данных может позволить безопасная вставка и удаление элементов, и он делает это путем блокировки структуры данных или некоторого ее подмножества, чтобы один поток мог выполнять необходимые манипуляции без вмешательства его изменений - и структура данных повреждена - во время работы. Однако для выполнения бизнес-функции может потребоваться удаление элемента из одной коллекции с последующей его вставкой в другую, а также атомарное выполнение всей операции. Проблема в том, что блокировка происходит только в данных структура. Вы можете безопасно удалить элемент из одного, но тогда вы можете обнаружить, что не можете вставить его в другой из-за некоторого нарушения ключа. Или вы можете попробовать вставить его в одну секунду, а затем удалить из первой, только чтобы обнаружить, что другая нить украла его из-под вашего носа. Осознав, что вы не можете завершить операцию, вы можете попытаться вернуть все так, как было, только чтобы обнаружить, что разворот терпит неудачу по аналогичным причинам, и теперь вы находитесь в подвешенном состоянии! Вы можно, конечно, реализовать более богатую схему блокировки, которая охватывает несколько структур данных, но это работает только в том случае, если все согласны с новой схемой блокировки и несут бремя ее использования все время, даже когда все их операции находятся на одной структуре данных.
мьютекс-стиль блокировки, таким образом, концепция, которая не составляет. Вы не можете реализовать потокобезопасное поведение более высокого уровня просто путем агрегирования потокобезопасных операций более низкого уровня. Решение в этом случае заключается в использовании концепция, которая действительно составляет, например СТМ.
Я согласен с ответом Марсело Кантоса, но я думаю, что он может предполагать больше фона, чем у некоторых читателей, что также связано с тем, почему композиция в функциональном программировании особенная. Функциональное программирование состав функций по существу идентичен составу функций в математике. В математике, вы можете иметь функцию
f(x) = x^2иg(x) = x + 1. Составление функций означает создание новой функции, в которой аргументы функции задаются "внутренней" функции, а выход "внутренней" функции служит входом для "внешней" функции. Сочинениеfвнешний сgвнутренний может быть написаноf(g(x)). Если вы предоставляете значение1наx, потомg(1) == 1 + 1 == 2, так чтоf(g(1)) == f(2) == 2^2 == 4. В более общем плане,f(g(x)) == f(x + 1) == (x+1)^2. Я описал композицию с помощьюf(g(x))синтаксис, но математики часто предпочитают другой синтаксис,(f . g)(x). Я думаю, это потому, что это делает более ясным, чтоf composed with gэто функция сама по себе, которая принимает один аргумент.функциональные программы построены полностью с использованием композиционных примитивов. Программа в Haskell-это, возможно, чрезмерно упрощенная функция, которая принимает в качестве аргумента среду выполнения и возвращает результат некоторых манипуляций с этой средой. (Грокинг этого утверждения потребует некоторого понимания монад.) Все остальное делается с композиция в математическом смысле.
другой пример: рассмотрим асинхронное программирование в. NET. если вы используете такой язык, как C#, и вам нужно сделать серию асинхронных (неблокирующих) вызовов ввода-вывода через API Begin/End, то для вызова двух операций
FooиBar, в последовательности, вы должны выставить два метода (BeginFooAndBar,EndFooAndBar), гдеBeginFooAndBarзвонкиBeginFooи передает обратный вызовIntermediateиIntermediateвызываетBeginBar, и вы должны нить промежуточные значения иIAsyncResultинформация о состоянии через, и если вы хотитеtry-catchблок вокруг всего этого, тогда удачи, вам нужно дублировать код обработки исключений в трех местах, упс, и это ужасный беспорядок.но тогда с F#, там
asyncтип, построенный поверх функциональных продолжений, которые могут быть составлены, и поэтому вы можете написать, например,let AsyncFooAndBar() = async { let! x = Async.FromBeginEnd(BeginFoo, EndFoo) let! y = Async.FromBeginEnd(BeginBar, EndBar, x) return y * 2 }или что у вас есть, и это просто, и если вы хотите поставить
try-catchвокруг него, отлично, код все в одном методе, а не чем распределить по трем, вы просто ставитеtry-catchвокруг него и его произведений.
вот реальный пример. Имена всех людей, которые живут в вашем доме-это список имен всех мужчин в вашем доме в сочетании со списком всех женщин в вашем доме.
это композиционно, так как каждая из этих двух подзадач может быть решена независимо и без вмешательства в решение другой.
с другой стороны, многие рецепты не составного, а шаги должны быть сделаны в определенном порядке и полагаться на результаты другие действия. Вы должны разбить яйца, прежде чем взбить их!
Composability позволяет сообществу разработчиков постоянно повышать уровень абстракции, несколько уровней, не будучи прикованным к базовому слою.
Comments