Считается ли замыкание с побочными эффектами «функциональным стилем»?

9

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

  1. Может рассматриваться как значение и, следовательно, храниться в переменной, передаваться различным частям кода, определяться в одной части программы и вызываться в совершенно другой части той же программы.
  2. Может захватывать переменные из контекста, в котором они определены, и получать к ним доступ при последующем вызове (возможно, в совершенно ином контексте).

Вот пример замыкания, написанного на Scala:

def filterList(xs: List[Int], lowerBound: Int): List[Int] =
  xs.filter(x => x >= lowerBound)

Литерал функции x => x >= lowerBoundсодержит свободную переменную lowerBound, которая закрыта (связана) аргументом функции filterListс тем же именем. Закрытие передается методу библиотеки filter, который может вызывать его повторно как обычную функцию.

Я читал много вопросов и ответов на этом сайте, и, насколько я понимаю, термин « закрытие» часто автоматически ассоциируется с функциональным программированием и функциональным стилем программирования.

Определение функции программирования в Википедии гласит:

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

и далее

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

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

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

Это использование замыканий с побочными эффектами считается функциональным стилем или замыкания считаются более общей конструкцией, которая может использоваться как для функционального, так и для нефункционального стиля программирования? Есть ли литература на эту тему?

ВАЖНАЯ ЗАМЕТКА

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

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

Джорджио
источник
3
В чисто функциональном стиле / языке побочные эффекты невозможны ... поэтому я думаю, что это будет вопрос: на каком уровне чистоты можно считать код функциональным?
Стивен Эверс
@SnOrfus: на 100%?
Джорджио
1
Побочные эффекты невозможны на чисто функциональном языке, поэтому следует ответить на ваш вопрос.
Стивен Эверс
@SnOrfus: Во многих языках замыкания считаются функциональной конструкцией, поэтому я был немного смущен. Мне кажется, что (1) их использование носит более общий характер, чем просто выполнение FP, и (2) использование замыканий не гарантирует автоматически использование функционального стиля.
Джорджио
Может ли downvoter оставить заметку о том, как улучшить этот вопрос?
Джорджио

Ответы:

7

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

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

Это, конечно, очень упрощенный вид.

m3th0dman
источник
3
Функции высшего порядка не имеют абсолютно никакого отношения к изменчивости, равно как и функции первого класса. Я могу с легкостью передавать функции и создавать из них другие функции в Haskell, не имея изменяемого состояния или побочных эффектов.
tdammers
@tdammers Я, наверное, не очень ясно выразился; когда я сказал, что функции стали изменяемыми, я имел в виду не изменчивость данных, а тот факт, что поведение функции можно изменить (с помощью функции высокого порядка).
m3th0dman
Функция высшего порядка не должна изменять существующую функцию; Большинство примеров из учебников объединяют существующие функции в новые функции, не изменяя их вообще. mapНапример, возьмем функцию, которая применяет ее к списку и возвращает список результатов. mapне изменяет ни один из своих аргументов, он не меняет поведение функции, которую он принимает в качестве аргумента, но это определенно функция высшего порядка - если вы примените ее частично, только с помощью параметра функции, вы построите новая функция, которая работает со списком, но мутации еще не произошло.
tdammers
@tdammers: Точно: изменчивость используется только в императивных или мультипарадигмальных языках. Даже если они могут иметь понятие функции или метода более высокого порядка и замыкания, они отказываются от неизменности. Это может быть полезно (я не говорю, что не следует этого делать), но мой вопрос заключается в том, можете ли вы по-прежнему вызывать этот функционал. Что означало бы, что в общем случае замыкание не является строго функциональной концепцией.
Джорджио
@Giorgio: большинство классических языков FP делать есть переменчивость; Хаскелл - единственный, о котором я могу подумать, что он не допускает изменчивости. Тем не менее, избегание изменчивого состояния является важным значением в FP.
tdammers
9

Нет. «Функциональный стиль» подразумевает программирование без побочных эффектов.

Чтобы понять почему, взгляните на запись Эрика Липперта в блоге о ForEach<T>методе расширения и о том , почему Microsoft не включила такой метод последовательности в Linq :

Я философски против предоставления такого метода по двум причинам.

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

Вторая причина в том, что это добавляет ноль новых репрезентативных возможностей для языка. Это позволяет вам переписать этот совершенно чистый код:

foreach(Foo foo in foos){ statement involving foo; }

в этот код:

foos.ForEach((Foo foo)=>{ statement involving foo; });

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

Роберт Харви
источник
1
Хотя я с ним согласен ... его аргумент немного ослабевает при рассмотрении ParallelQuery<T>.ForAll(...). Реализация такой IEnumerable<T>.ForEach(...)чрезвычайно полезна для отладки ForAllзаявления (заменить ForAllс ForEachи удалить , AsParallel()и вы можете гораздо легче пошагово / отладить)
Стивен Evers
Очевидно, у Джо Даффи разные идеи. : D
Роберт Харви
Scala также не обеспечивает чистоту: вы можете передать не чистое замыкание в функцию высокого порядка. У меня сложилось впечатление, что идея замыкания не является специфической для функционального программирования, но это более общая идея.
Джорджио
5
@ Джорджио: Закрытия не должны быть чистыми, чтобы все еще считаться замыканиями. Однако они должны быть чистыми, чтобы считаться «функциональным стилем».
Роберт Харви
3
@ Джорджио: Это действительно полезно иметь возможность сделать замыкание вокруг изменчивого состояния и побочных эффектов и передать его другой функции. Это просто противоречит целям функционального программирования. Я думаю, что большая путаница возникает из-за элегантной языковой поддержки лямбд, распространенной в функциональных языках.
ГленПетерсон
0

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

Если единственное, что вы делаете, это вычисляете математические формулы в REPL, то вы можете быть функционально чисты с вашим языком. Но у большинства бизнес-программ есть побочные эффекты. Потеря денег при ожидании завершения долгосрочной программы является побочным эффектом. Выполнение любых внешних действий: запись в файл, обновление базы данных, регистрация событий по порядку и т. Д. Требуют изменения состояния. Мы могли бы обсудить, действительно ли изменено состояние, если вы заключите эти действия в неизменяемые обертки, которые отталкивают побочные эффекты, так что вашему коду не нужно беспокоиться о них. Но это все равно, что обсуждать, шумит ли дерево, если оно падает в лесу, где его никто не слышит. Дело в том, что дерево встало вертикально и упало на землю. Состояние будет изменено, когда что-то будет сделано, даже если только сообщать, что это сделано.

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

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

GlenPeterson
источник