Как функциональные языки моделируют побочные эффекты?
Поскольку побочные эффекты нарушают ссылочную прозрачность, не противоречат ли они функциональным языкам?
Ответы
Ответ 1
Существует два метода, которые используются чисто функциональными языками программирования для моделирования побочных эффектов:
1) Тип мира, представляющий внешнее состояние, где каждое значение этого типа гарантируется системой типов, которая будет использоваться только один раз.
В языке, использующем этот подход, функции print
и read
могут иметь типы (string, world) -> world
и world -> (string, world)
соответственно.
Они могут быть использованы следующим образом:
let main w =
let w1 = print ("What your name?", w) in
let (str, w2) = read w1 in
let w3 = print ("Your name is " ^ name, w2) in
w3
Но не так:
let main w =
let w1 = print ("What your name?", w) in
let (str, w2) = read w in
let w3 = print ("Your name is " ^ name, w2) in
w3
(поскольку w используется дважды)
Все встроенные функции с побочными эффектами будут принимать и возвращать мировое значение. Поскольку все функции с побочными эффектами являются встроенными или вызовами других функций с побочными эффектами, это означает, что все функции с побочными эффектами должны принимать и возвращать мир.
Таким образом, невозможно вызвать функцию с побочными эффектами дважды с теми же аргументами, и ссылочная прозрачность не нарушена.
2) Монада IO, где все операции с побочными эффектами должны выполняться внутри этой монады.
При таком подходе все операции с побочными эффектами будут иметь тип io something
. Например, print
будет функцией с типом string -> io unit
, а read
будет иметь тип io string
.
Единственный способ получить доступ к значению выполняемой операции будет заключаться в использовании операции "monadic bind" (например, → = в haskell) с операцией ввода-вывода в качестве одного аргумента и функцией, описывающей, что делать с результатом как другой операнд.
Приведенный выше пример будет похож на это с монадическим IO:
let main =
(print "What your name?") >>=
(lambda () -> read >>=
(lambda name -> print ("Your name is " ^ name)))
Ответ 2
Существует несколько вариантов обработки ввода-вывода на функциональном языке.
- Не будь чистым. Многие функциональные языки не являются чисто функциональными. Более того, они поддерживают
а не обеспечивать его соблюдение. Это, безусловно, наиболее распространенное решение проблемы
ввода-вывода в функциональном программировании. (Примеры: Lisp, схема, стандартный ML, Erlang и т.д.).
- Преобразование потока. Ранний ввод-вывод Haskell был выполнен таким образом. Проверьте мою ссылку ниже, если вы
хочу больше информации. (Подсказка: вы, вероятно, этого не делаете.)
- Продолжающий передачу ввода-вывода ( "переходящий мир", упомянутый в других ответах). В этом вы передаете токен
данных вокруг вашего ввода-вывода, который действует как необходимое "другое значение" для сохранения ссылочной целостности
в живых. Это используется несколькими диалектами ML, если память служит.
- "Продолжение" или "мировая" вещь выше может быть обернута в различные типы данных, большинство (в) известных
использование монад в этой роли в Haskell. Обратите внимание, что это, по-видимому, то же самое в
обложки, но угасание следа переменных состояния "мир" / "продолжение" удаляется.
Там исследовательская диссертация, которая исчерпывающе анализирует их.
Функциональный ввод-вывод - это постоянная область исследований, и есть другие языки, которые затрагивают этот вопрос интересными и увлекательными способами. Логика Хора используется для использования на некоторых языках исследования. Другие (например, Mercury) используют уникальность ввода. Третьи (например, Clean) используют системы эффектов. Из них у меня очень ограниченное воздействие только на Меркурий, поэтому я не могу комментировать подробности. Там бумага, в которой подробно описывается система чистого ввода-вывода, однако, если вы заинтересованы в этом направлении.
Ответ 3
Насколько я понимаю, если вы хотите иметь побочные эффекты на функциональном языке, вам нужно явно их кодировать.