Что значит для чего-то «хорошо сочинять»? - PullRequest
15 голосов
/ 06 ноября 2010

Много раз я сталкивался с утверждениями вида Х хорошо / плохо сочиняет.

Я помню несколько примеров, которые я недавно читал:

  • Макросы плохо сочетаются (контекст: clojure)
  • Замки плохо сочетаются (контекст: clojure)
  • Императивное программирование плохо сочетается ... и т. Д.

Я хочу понять последствия компоновки с точки зрения проектирования / чтения / записи кода? Примеры были бы хорошими.

Ответы [ 7 ]

10 голосов
/ 06 ноября 2010

«Составление» функций - это просто объединение двух или более функций для создания большой функции, которая полезным образом объединяет их функции.По сути, вы определяете последовательность функций и перенаправляете результаты каждой из них в следующую, в конце концов, давая результат всего процесса.Clojure предоставляет функцию comp, чтобы сделать это за вас, вы также можете сделать это вручную.

Функции, которые вы можете творчески связать с другими функциями, в целом более полезны, чем функции, которые вы можете вызвать тольков определенных условиях.Например, если у нас не было функции last и были только традиционные функции списка Lisp, мы могли бы легко определить last как (def last (comp first reverse)).Посмотрите на это - нам даже не нужно было defn или упоминать какие-либо аргументы, потому что мы просто передаем результат одной функции в другую.Это не будет работать, если, например, reverse выбрал обязательный путь изменения последовательности на месте.Макросы также проблематичны, потому что вы не можете передать их таким функциям, как comp или apply.

3 голосов
/ 06 ноября 2010

Композиция в программировании означает сборку больших кусков из меньших.

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

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

Предположим, у меня есть простой код C.

void run_with_resource(void) {
  Resource *r = create_resource();
  do_some_work(r);
  destroy_resource(r);
}

C облегчает композиционные рассуждения о потоке управления на уровне функций. Мне не нужно заботиться о том, что на самом деле происходит внутри do_some_work (); Я просто знаю по этой маленькой функции, что каждый раз, когда ресурс создается в строке 2 с помощью create_resource (), он в конечном итоге уничтожается в строке 4 destroy_resource ().

Ну, не совсем. Что если create_resource () получает блокировку, а destroy_resource () освобождает ее? Тогда мне нужно беспокоиться о том, получит ли do_some_work такую ​​же блокировку, которая помешала бы завершению функции. Что если do_some_work () вызывает longjmp () и полностью пропускает конец моей функции? Пока я не знаю, что происходит в do_some_work (), я не смогу предсказать поток управления моей функции. У нас больше нет композиционности: мы больше не можем разбивать программу на части, самостоятельно рассуждать о частях и переносить наши выводы обратно на всю программу. Это значительно усложняет проектирование и отладку, и поэтому людям важно, хорошо ли что-то сочиняется.

2 голосов
/ 06 ноября 2010

Подумайте о том, когда вы пишете функции или методы. Вы создаете группу функций для выполнения конкретной задачи. При работе на объектно-ориентированном языке вы группируете свое поведение вокруг действий, которые, по вашему мнению, будет выполнять отдельная сущность в системе. Функциональные программы выходят из этого, поощряя авторов группировать функциональность в соответствии с абстракцией. Например, библиотека Clojure Ring содержит группу абстракций, которые охватывают маршрутизацию в веб-приложениях.

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

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

2 голосов
/ 06 ноября 2010

«Удар по баксу» - хорошая компоновка подразумевает высокий коэффициент выразительности на одно правило композиции.Каждый макрос вводит свои правила композиции.Каждая пользовательская структура данных делает то же самое.Функции, особенно те, которые используют общие структуры данных, имеют гораздо меньше правил.

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

2 голосов
/ 06 ноября 2010

Композиция (в контексте, который вы описываете на функциональном уровне) - это, как правило, возможность аккуратно и без промежуточной обработки передавать одну функцию в другую.Такой пример композиции находится в std :: cout в C ++:

cout << каждый << элемент << ссылки << on; </p>

Это простой пример композиции, который не 't действительно "выглядит" как композиция.

Еще один пример с более заметной композиционной формой:

foo (bar (baz ()));

Wikipedia Link

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

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


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

1 голос
/ 07 ноября 2010

компонуемость , примененная к функциям, означает, что функции меньше и четко определены, поэтому их легко интегрировать в другие функции (я видел эту идею в книге «Радость замыкания»)

концепция может применяться к другим вещам, которые, как предполагается, должны быть составлены во что-то другое.

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

макросы не так хорошо компонуются, потому что вы не можете передать их в качестве параметров

lock - чушь, потому что вы не можете дать им имена (определить их правильно) или использовать их повторно. вы просто делаете их на месте

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

другая идея в императивных языках заключается в том, что они плохо сочетаются, поскольку подразумевают состояние (из wikipedia база знаний :) "Императивное программирование - описывает вычисления с точки зрения утверждений, которые изменяют состояние программы ").

состояние плохо составляется, потому что, хотя вы дали конкретное «что-то» на входе, это «что-то» генерирует вывод в соответствии с его состоянием. другое внутреннее состояние, другое поведение. и, таким образом, вы можете попрощаться с тем, что вы ожидаете.

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

ps: думаешь об изучении clojure, а? расследовать ...? повезло тебе ! : P

1 голос
/ 06 ноября 2010

В контексте clojure, этот комментарий затрагивает определенные аспекты сочетаемости.В общем, это, кажется, возникает, когда единицы системы хорошо выполняют одну задачу, не требуя, чтобы другие единицы понимали ее внутренние особенности, избегали побочных эффектов, а также принимали и возвращали распространяющиеся структуры данных системы.Все вышеперечисленное можно увидеть в примере C ++ для M2tM.

...