Являются ли замыкания нечистыми в функциональном программировании?
Кажется, что можно вообще избежать замыканий, передавая значения непосредственно в функцию. Поэтому следует ли по возможности избегать закрытия?
Если они нечисты, и я правильно заявляю, что их можно избежать, почему так много функциональных языков программирования поддерживают замыкания?
Один из критериев для чистой функции заключается в том, что «функция всегда оценивает одно и то же значение результата при одинаковых значениях аргумента ».
предполагать
f: x -> x + y
f(3)
не всегда даст один и тот же результат. f(3)
зависит от значения y
которого не является аргументом f
. Таким образом f
, не является чистой функцией.
Поскольку все замыкания основаны на значениях, которые не являются аргументами, как возможно, чтобы любое замыкание было чистым? Да, теоретически закрытое значение может быть постоянным, но невозможно узнать это, просто посмотрев исходный код самой функции.
Это приводит меня к тому, что одна и та же функция может быть чистой в одной ситуации, но нечистой в другой. Не всегда можно определить, является ли функция чистой или нет, изучая ее исходный код. Скорее, может потребоваться рассмотреть его в контексте его окружения в тот момент, когда он вызывается, прежде чем можно будет провести такое различие.
Я правильно об этом думаю?
источник
y
изменить нельзя, поэтому выводf(3)
всегда будет одинаковым.y
является частью определения,f
даже если он явно не помечен как вход дляf
- это все еще тот случай, которыйf
определен в терминахy
(мы могли бы обозначить функцию f_y, чтобы сделать зависимостьy
явным), и поэтому изменениеy
дает другую функцию , Определенная функция,f_y
определенная для конкретногоy
, очень чистая. (Например, две функцииf: x -> x + 3
иf: x -> x + 5
являются разными функциями, и обе являются чистыми, хотя мы случайно использовали одну и ту же букву для их обозначения.)Ответы:
Чистота может быть измерена двумя вещами:
Если ответ на 1 - «да», а ответ на «2» - нет, то функция является чистой. Замыкания делают функцию нечистой, если вы изменяете закрытую переменную.
источник
y
. Так, например, мы определяем функциюg
, котораяg(y)
сама является функциейx -> x + y
. Тогдаg
есть функция , которая возвращает целые числа функций,g(3)
является функцией , которая возвращает целые числа, иg(2)
это другая функция , которая возвращает целые числа. Все три функции чистые.Появляются замыкания Lambda Calculus, которые являются самой чистой формой функционального программирования, поэтому я бы не назвал их «нечистыми» ...
Замыкания не являются «нечистыми», потому что функции в функциональных языках являются гражданами первого сорта - это означает, что их можно рассматривать как ценности.
Представьте себе это (псевдокод):
y
это значение. Это значение зависит отx
, ноx
является неизменным, поэтомуy
значение также является неизменным. Мы можем вызыватьfoo
много раз с разными аргументами, которые приведут к различнымy
s, ноy
все они живут в разных областях и зависят от разныхx
s, поэтому чистота остается неизменной.Теперь давайте изменим это:
Здесь мы используем замыкание (мы закрываем по x), но это то же самое, что и в
foo
- разные вызовыbar
с разными аргументами создают разные значенияy
(помните - функции являются значениями), которые все неизменяемы, поэтому чистота остается неизменной.Также обратите внимание, что у замыканий эффект схож с карри:
baz
на самом деле не отличается отbar
- в обоих мы создаем значение функции с именем,y
которое возвращает его аргумент плюсx
. На самом деле, в Lambda Calculus вы используете замыкания для создания функций с несколькими аргументами - и это все еще не является нечистым.источник
Другие хорошо ответили на общий вопрос в своих ответах, поэтому я буду только смотреть на устранение путаницы, которую вы сигнализируете при редактировании.
Замыкание не становится входом функции, скорее оно «входит» в тело функции. Чтобы быть более конкретным, функция ссылается на значение во внешней области видимости в своем теле.
У вас сложилось впечатление, что это делает функцию нечистой. Это не так, в общем. В функциональном программировании значения являются неизменяемыми большую часть времени . Это относится и к закрытому значению.
Допустим, у вас есть кусок кода, подобный этому:
Вызов
make 3
иmake 4
даст вам две функции с закрытием надmake
«Sy
аргумент. Один из них вернетсяx + 3
, другойx + 4
. Однако это две разные функции, и обе они чистые. Они были созданы с использованием одной и той жеmake
функции, но это все.Обратите внимание, большую часть времени несколько абзацев назад.
Обратите внимание, что для 2 и 3 эти языки не дают никаких гарантий относительно чистоты. Примесь там не является свойством замыкания, но самого языка. Закрытия сами по себе не сильно меняют картину.
источник
IO A
значение, и ваш тип закрытия являетсяIO (B -> C)
или что-то подобное. Чистота поддерживаетсяОбычно я прошу вас уточнить ваше определение «нечистого», но в этом случае это не имеет значения. Предполагая, что вы противопоставляете его термину « чисто функциональный» , ответ «нет», потому что нет ничего о замыканиях, которые по своей природе разрушительны. Если бы ваш язык был чисто функциональным без замыканий, он все равно был бы чисто функциональным с замыканиями. Если вместо этого вы имеете в виду «не функционально», ответ по-прежнему «нет»; замыкания облегчают создание функций.
Да, но тогда у вашей функции будет еще один параметр, и это изменит его тип. Замыкания позволяют создавать функции на основе переменных без добавления параметров. Это полезно, например, когда у вас есть функция, которая принимает 2 аргумента, и вы хотите создать ее версию, которая принимает только 1 аргумент.
РЕДАКТИРОВАТЬ: Что касается вашего собственного редактирования / пример ...
Зависит от неправильного выбора слова здесь. Цитирую ту же статью в Википедии, что и вы:
Предполагая, что
y
является неизменным (что обычно имеет место в функциональных языках), условие 1 выполняется: для всех значенийx
значениеf(x)
не изменяется. Это должно быть ясно из того факта, чтоy
ничем не отличается от константы иx + 3
является чистым. Также ясно, что здесь нет мутаций или операций ввода-вывода.источник
Очень быстро: подстановка «прозрачна по ссылкам», если «подстановка подобного приводит к подобию», а функция «чиста», если все ее эффекты содержатся в возвращаемом значении. Оба из них могут быть сделаны точными, но важно отметить, что они не идентичны и даже не подразумевают другого.
Теперь поговорим о замыканиях.
Скучные (в основном чистые) "замыкания"
Замыкания происходят потому, что когда мы оцениваем лямбда-член, мы интерпретируем (связанные) переменные как поиск окружения. Таким образом, когда мы возвращаем лямбда-член в результате оценки, переменные внутри него будут «закрывать» значения, которые они приняли, когда он был определен.
В простом лямбда-исчислении это тривиально, и само понятие просто исчезает. Чтобы продемонстрировать это, вот относительно легкий интерпретатор лямбда-исчисления:
Важная часть, на которую следует обратить внимание, - это
addEnv
когда мы дополняем среду новым именем. Эта функция вызывается только «внутри» интерпретируемогоAbs
тягового термина (лямбда-термина). Окружение «смотрится» всякий раз, когда мы оцениваемVar
термин, и поэтому ониVar
решают все, на чтоName
ссылается то,Env
что было захваченоAbs
тягой, содержащейVar
.Теперь, опять же, в простых терминах ЛНР, это скучно. Это означает, что связанные переменные являются просто константами, насколько это кого-либо волнует. Они оцениваются непосредственно и сразу как значения, которые они обозначают в среде, как лексически ограниченные до этой точки.
Это также (почти) чисто. Единственное значение любого термина в нашем лямбда-исчислении определяется его возвращаемым значением. Единственным исключением является побочный эффект не прекращения, который воплощен в омега-терминах:
Интересные (нечистые) замыкания
Теперь для некоторых фонов замыкания, описанные в простом LC выше, скучны, потому что нет понятия возможности взаимодействовать с переменными, которые мы закрыли. В частности, слово «закрытие» имеет тенденцию вызывать код, подобный следующему Javascript
Это показывает, что мы закрыли
n
переменную во внутренней функции,incr
и вызовincr
содержательно взаимодействует с этой переменной.mk_counter
чисто, ноincr
явно нечисто (и референтно не прозрачно).Чем отличаются эти два случая?
Понятия "переменная"
Если мы посмотрим, что означают подстановка и абстракция в простом смысле слова LC, мы заметим, что они явно просты. Переменные буквально не более чем непосредственный поиск окружения. Лямбда-абстракция - это буквально не что иное, как создание расширенной среды для оценки внутреннего выражения. В этой модели нет места для того поведения, которое мы видели с
mk_counter
/,incr
потому что не допускается никаких изменений.Для многих это является сердцем того, что означает «переменная» - вариация. Тем не менее, семантикам нравится различать тип переменной, используемой в LC, и тип «переменной», используемой в Javascript. Для этого они, как правило, называют последнюю «изменяемой ячейкой» или «щелью».
Эта номенклатура следует долгому историческому использованию «переменной» в математике, где оно означало нечто более похожее на «неизвестный»: (математическое) выражение «x + x» не допускает
x
изменения во времени, вместо этого оно должно иметь значение независимо из (одного, постоянного) значенияx
принимает.Таким образом, мы говорим «слот», чтобы подчеркнуть способность помещать значения в слот и выводить их.
Чтобы добавить еще больше к путанице, в Javascript эти «слоты» выглядят так же, как переменные: мы пишем
создать один, а затем, когда мы пишем
это указывает на то, что мы ищем значение, которое в данный момент хранится в этом слоте. Чтобы сделать это более понятным, чистые языки склонны думать о слотах как об именах как (математических, лямбда-исчисление) имен. В этом случае мы должны явно пометить, когда мы получаем или кладем из слота. Такие обозначения имеют тенденцию выглядеть
Преимущество этой записи в том, что теперь у нас есть четкое различие между математическими переменными и изменяемыми слотами. Переменные могут принимать значения слотов в качестве своих значений, но конкретный слот, названный переменной, является постоянным во всей своей области видимости.
Используя эту нотацию, мы можем переписать
mk_counter
пример (на этот раз в синтаксисе, подобном Haskell, хотя и явно не в семантике, подобной Haskell):В этом случае мы используем процедуры, которые манипулируют этим изменяемым слотом. Чтобы реализовать это, нам нужно закрыть не только постоянную среду имен,
x
но и изменчивую среду, содержащую все необходимые слоты. Это ближе к общему понятию «закрытие», которое люди так любят.Опять
mkCounter
очень нечисто. Это также очень непрозрачно. Но обратите внимание, что побочные эффекты возникают не в результате захвата или закрытия имени, а в результате захвата изменяемой ячейки и побочных операций над ней, таких какget
иput
.В конечном счете, я думаю, что это окончательный ответ на ваш вопрос: на чистоту не влияет (математический) захват переменных, а вместо этого побочные эффекты, выполняемые с изменяемыми слотами, названными захваченными переменными.
Только в тех языках, которые не пытаются быть близкими к ЛНР или не пытаются поддерживать чистоту, эти два понятия так часто смешиваются, что приводит к путанице.
источник
Нет, замыкания не приводят к нечистоте функции, пока закрытое значение является постоянным (не изменяется ни замыканием, ни другим кодом), что является обычным случаем в функциональном программировании.
Обратите внимание, что, хотя вы всегда можете вместо этого передать значение в качестве аргумента, обычно вы не можете сделать это без значительных трудностей. Например (coffeescript):
По вашему предложению вы можете просто вернуться:
Эта функция не называется в этой точке, просто определено , так что вам придется найти способ , чтобы передать нужное значение для
closedValue
точки , в которой функция фактически называется. В лучшем случае это создает много связей. В худшем случае вы не контролируете код в точке вызова, поэтому это практически невозможно.Библиотеки событий на языках, которые не поддерживают замыкания, обычно предоставляют какой-то другой способ передачи произвольных данных обратно в обратный вызов, но это не красиво, и создает большую сложность как для сопровождающего, так и для пользователя библиотеки.
источник