Функциональное программирование: состояние и переназначение

Мне нужна помощь, чтобы разглядеть разницу между моим текущим понятием состояния ООП и тем, как это будет сделано на функциональном языке, таком как Haskell или Clojure.

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

Итак... что мне с этим делать? Перезаписать любую переменную, ссылаясь на старый банковский счет? Если да, то имеет ли это преимущества по сравнению с изменяющимся в государстве подходом ООП? В конце концов, в обоих случаях кажется, что у одного есть переменная, которая ссылается на объект с необходимыми изменениями. Затормозившись, как и я, у меня есть только смутное представление о том, что происходит.

Я надеюсь, что это имело смысл, спасибо за любую помощь!

4 ответа

В чистом функциональном стиле вы никогда не перезапишете какую-либо переменную.

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

Конечно, детали того, как связать последовательность значений с "идентичностью объекта", могут быть немного волосатыми. Haskell имеет Monads, которые позволяют моделировать состояние. Функциональное реактивное программирование - это более буквальная попытка моделирования объектов в мире с чистыми функциональными обновлениями, которые я считаю очень перспективным направлением для программирования.

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


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

updatePortfolio :: Request -> Portfolio -> Portfolio

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

readRequest :: IO Request -- an action that, when performed, reads a Request with side effects
main :: Portfolio -> IO () -- a completely useless program that updates a Portfolio in response to a stream of Requests
main portfolio = do req <- readRequest
 main (updatePortfolio req)

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

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


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

Да

Если это так, имеет ли они преимущества перед изменяющимся положением ООП?

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

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

Кроме того, мы можем сэкономить место, используя данные из другой структуры, из которой мы копируем. Классическим примером является добавление элемента в начало списка. Если у нас есть указатель на второй элемент и указатель на первый элемент, мы можем ссылаться на оба списка только с размером первого (см. Ниже). Без непреложности мы не можем этого гарантировать.

b__
 | 
a -> [6|] -+-> [5|] -> [4|] -> [3|] -> [2|] -> [1|x]


Посмотрите на Haskell, который является чистым функциональным языком - он не имеет никакого повторного назначения вообще, а также никаких других побочных эффектов: чтобы сделать IO, в IO monad фактически заменяет RealWorld новым экземпляром мира, который имеет, например, новый текст, отображаемый в консоли.

licensed under cc by-sa 3.0 with attribution.