Что значит "что-то хорошо сочинять"?

Много раз, я встречал заявления формы X действительно/не соответствует.

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

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

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

Ответы

Ответ 1

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

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

Ответ 2

Состав в программировании означает сбор больших партий из меньших.

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

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

Предположим, что у меня есть простой 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(), я не смогу предсказать поток управления моей функцией. У нас больше нет составности: мы больше не можем разлагать программу на части, рассуждать о деталях самостоятельно и выводить наши выводы на всю программу. Это значительно усложняет проектирование и отладку, и почему люди заботятся о том, что хорошо складывается.

Ответ 3

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

cout < каждый < элемент < ссылки < на

Это простой пример композиции, которая на самом деле не похожа на композицию.

Другой пример с формой более видимой композиции:

Foo (бар (БАЗ()));

Ссылка Википедии

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

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


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

Ответ 4

"Bang for the Buck" - хорошая композиция подразумевает высокое отношение выразительности к правилу композиции. Каждый макрос вводит свои собственные правила композиции. Каждая пользовательская структура данных делает то же самое. Функции, особенно те, которые используют общие структуры данных, имеют гораздо меньше правил.

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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

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

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

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

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

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

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

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