功能语言如何模拟副作用?

use*_*230 22 io functional-programming referential-transparency

由于副作用破坏了参考透明度,它们是否违背功能语言的观点?

sep*_*p2k 23

纯函数式编程语言使用两种技术来模拟副作用:

1)表示外部状态的世界类型,其中该类型的每个值由类型系统保证仅使用一次.

在使用这种方法的功能的语言print,并read可能具有类型(string, world) -> worldworld -> (string, world)分别.

它们可能会像这样使用:

let main w =
  let w1 = print ("What's your name?", w) in
  let (name, w2) = read w1 in
  let w3 = print ("Your name is " ^ name, w2) in
  w3
Run Code Online (Sandbox Code Playgroud)

但不是这样的:

let main w =
  let w1 = print ("What's your name?", w) in
  let (name, w2) = read w in
  let w3 = print ("Your name is " ^ name, w2) in
  w3
Run Code Online (Sandbox Code Playgroud)

(因为w使用了两次)

所有带副作用的内置函数都将获取并返回世界值.由于具有副作用的所有函数都是内置函数或调用具有副作用的其他函数,这意味着所有具有副作用的函数都需要获取并返回一个世界.

这样就不可能使用相同的参数调用带有副作用的函数两次,并且不会违反参照透明度.

2)IO monad,其中所有具有副作用的操作必须在该monad内执行.

使用这种方法,所有带副作用的操作都会有类型io something.例如,print将是一个类型的函数,string -> io unitread具有类型io string.

访问执行操作值的唯一方法是使用"monadic bind"操作(例如,在haskell中称为>> =),IO操作作为一个参数,并且函数描述如何处理结果作为另一个操作数.

上面的例子看起来像monadic IO:

let main =
  (print "What's your name?") >>=
  (lambda () -> read >>=
  (lambda name -> print ("Your name is " ^ name)))
Run Code Online (Sandbox Code Playgroud)

  • 你的#2是你的#1的实现细节.IO monad会自动为您自动携带该世界状态,根据您的monad所使用的语法,根据需要打包和解包. (4认同)
  • @JUST:好的,我明白你的观点,#2是对#1的抽象,但这也不一定正确.例如,使用monadic IO的语言不需要实现唯一性类型,以确保每个世界只使用一次,只要它们不向程序员公开世界类型即可. (2认同)

JUS*_*ION 13

有几种选项可用于处理函数式语言中的I/O.

  • 不要纯洁.许多功能语言不是纯粹的功能.它更多的是它们支持 函数式编程而不是强制执行.到目前为止,这是函数式编程中I/O问题的最常见解决方案.(示例:Lisp,Scheme,Standard ML,Erlang等)
  • 流转换.早期的Haskell I/O就是这样完成的.如果您想了解更多信息,请查看下面的链接了解详情.(提示:你可能没有.)
  • 继续传递I/O(其他答案中提到的"世界传递").在这一个中,您使用I/O传递数据标记,该I/O充当必要的"不同值"以保持参照完整性.如果存储器有效,则由几种ML方言使用.
  • 上面的"延续"或"世界"事物可以包含在各种数据类型中,最常见的是在Haskell中使用monad这个角色.请注意,这在概念上是相同的,但是删除了跟踪"世界"/"延续"状态变量的单调乏味.

一篇研究论文详尽地分析了这些.

功能I/O是一个正在进行的研究领域,还有其他语言以有趣和令人头疼的方式解决了这个问题. Hoare逻辑用于一些研究语言.其他人(如Mercury)使用唯一性输入.还有一些(如 清洁)使用效果系统.其中我对水星的接触非常非常有限,所以我无法对细节做出真正的评论.有一篇论文详细介绍了Clean的I/O系统,但是,如果你对这个方向感兴趣的话.