Функциональное программирование включает в себя множество различных методов. Некоторые методы хороши с побочными эффектами. Но одним важным аспектом является логическое обоснование : если я вызываю функцию с одним и тем же значением, я всегда получаю один и тот же результат. Поэтому я могу заменить вызов функции возвращаемым значением и получить эквивалентное поведение. Это облегчает рассуждения о программе, особенно при отладке.
Если у функции есть побочные эффекты, это не совсем верно. Возвращаемое значение не эквивалентно вызову функции, поскольку возвращаемое значение не содержит побочных эффектов.
Решением является прекращение использования побочных эффектов и кодирование этих эффектов в возвращаемом значении . Разные языки имеют разные системы эффектов. Например, 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, не поощряют, но допускают побочные эффекты - их можно назвать императивно-функциональными языками.
Использование побочных эффектов для ввода / вывода, вероятно, хорошо. Часто ввод / вывод (кроме регистрации) выполняется только на внешней границе вашей системы. В вашей бизнес-логике не происходит внешних коммуникаций. Тогда можно написать ядро вашего программного обеспечения в чистом стиле, все еще выполняя нечистый ввод-вывод во внешней оболочке. Это также означает, что ядро может не иметь состояния.
Безгражданство имеет ряд практических преимуществ, таких как повышенная разумность и масштабируемость. Это очень популярно для бэкэндов веб-приложений. Любое состояние хранится снаружи, в общей базе данных. Это облегчает балансировку нагрузки: мне не нужно привязывать сессии к определенному серверу. Что если мне нужно больше серверов? Просто добавьте другой, потому что он использует ту же базу данных. Что делать, если один сервер выходит из строя? Я могу повторить любые отложенные запросы на другом сервере. Конечно, еще есть состояние - в базе данных. Но я сделал это явным и извлек его, и мог бы использовать чисто функциональный подход внутри, если захочу.
Ни один язык программирования не устраняет побочные эффекты. Я думаю, что лучше сказать, что декларативные языки содержат побочные эффекты, а императивные языки - нет. Однако я не уверен, что любой из этих разговоров о побочных эффектах приводит к фундаментальному различию между двумя типами языков, и это действительно похоже на то, что вы ищете.
Я думаю, что это помогает проиллюстрировать разницу на примере.
Вышеупомянутая строка кода может быть написана практически на любом языке, так как мы можем определить, используем ли мы императивный или декларативный язык? Чем отличаются свойства этой строки кода в двух классах языка?
В императивном языке (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
.источник