Функциональные языки программирования запрещают побочные эффекты?


10

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

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

Но, кроме того, у побочного эффекта есть другое определение. Побочный эффект

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

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

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

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

Ответы:


26

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

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

Решением является прекращение использования побочных эффектов и кодирование этих эффектов в возвращаемом значении . Разные языки имеют разные системы эффектов. Например, Haskell использует монады для кодирования определенных эффектов, таких как IO или State mutation. Языки C / C ++ / Rust имеют систему типов, которая может запретить изменение некоторых значений.

На императивном языке print("foo")функция печатает что-либо и ничего не возвращает. В чистом функциональном языке, таком как Haskell, printфункция также принимает объект, представляющий состояние внешнего мира, и возвращает новый объект, представляющий состояние, после выполнения этого вывода. Нечто подобное newState = print "foo" oldState. Я могу создать столько новых состояний из старого состояния, сколько захочу. Однако только одна из них будет использоваться основной функцией. Поэтому мне нужно упорядочить состояния из нескольких действий путем объединения функций. Для печати foo barя мог бы сказать что-то вроде print "bar" (print "foo" originalState).

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

Обратите внимание, что Haskell - единственный широко используемый функциональный язык, который использует этот маршрут. Другие функциональные языки вкл. Семейство Lisp, семейство ML и более новые функциональные языки, такие как Scala, не поощряют, но допускают побочные эффекты - их можно назвать императивно-функциональными языками.

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

Безгражданство имеет ряд практических преимуществ, таких как повышенная разумность и масштабируемость. Это очень популярно для бэкэндов веб-приложений. Любое состояние хранится снаружи, в общей базе данных. Это облегчает балансировку нагрузки: мне не нужно привязывать сессии к определенному серверу. Что если мне нужно больше серверов? Просто добавьте другой, потому что он использует ту же базу данных. Что делать, если один сервер выходит из строя? Я могу повторить любые отложенные запросы на другом сервере. Конечно, еще есть состояние - в базе данных. Но я сделал это явным и извлек его, и мог бы использовать чисто функциональный подход внутри, если захочу.


Спасибо за подробный ответ. В качестве выводов я заключаю, что побочные эффекты не влияют на значение функции из-за эквалайзера, поэтому «функциональные языки не допускают / минимизируют побочные эффекты». Эффекты, встроенные в значения функции, влияют и изменяют состояние, которое когда-либо сохраняется - или сохраняется вне ядра программы. Кроме того, ввод-вывод происходит на внешней границе бизнес-логики.
codebot

3
@codebot, не совсем, на мой взгляд. При правильной реализации побочные эффекты в функциональном программировании должны быть отражены в типе возврата функции. Например, если функция может дать сбой (если определенный файл не существует или соединение с базой данных не может быть установлено), то тип возвращаемого значения функции должен инкапсулировать ошибку, а не вызывать исключение функцией. Взгляните на железнодорожное программирование для примера ( fsharpforfunandprofit.com/posts/recipe-part2 ).
Аарон М. Эшбах

«... их можно назвать императивно-функциональными языками», - писал Саймон Пейтон Джонс. «... Haskell - лучший в мире язык императивного программирования».
Джорджио

5

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

Я думаю, что это помогает проиллюстрировать разницу на примере.

a = b + c

Вышеупомянутая строка кода может быть написана практически на любом языке, так как мы можем определить, используем ли мы императивный или декларативный язык? Чем отличаются свойства этой строки кода в двух классах языка?

В императивном языке (C, Java, Javascript и т. Д.) Эта строка кода просто представляет собой шаг в процессе. Это ничего не говорит нам о фундаментальной природе каких-либо ценностей. Это говорит нам о том, что в данный момент после этой строки кода (но до следующей строки) aбудет равно bплюс, cно это ничего не говорит нам aв более широком смысле.

На декларативном языке (Haskell, Scheme, Excel и т. Д.) Эта строка кода говорит о многом. Он устанавливает инвариантные отношения между aдвумя другими объектами, так что всегда будет aравен bплюс c. Обратите внимание, что я включил Excel в список декларативных языков, потому что даже если bили cизменит значение, факт останется фактом, который aбудет равен их сумме.

На мой взгляд, это не побочные эффекты или состояние, это то, что отличает два типа языков. В императивном языке любая конкретная строка кода ничего не говорит вам об общем значении рассматриваемых переменных. Другими словами, a = b + cтолько означает, что в течение очень короткого момента времени aпроизошло совпадение суммы bи c.

Между тем, в декларативных языках каждая строка кода устанавливает фундаментальную истину, которая будет существовать на протяжении всей жизни программы. На этих языках a = b + cговорит вам, что независимо от того, что происходит в любой другой строке кода a, всегда будет равно сумме bи c.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.