函数式编程:副作用究竟发生在哪里?

Functional programming : Where does the side effect actually happen?

开始学习Haskell后,虽然看了很多文档,但还是有一些不明白的地方Haskell

我知道要执行 IO 操作,您必须使用 "IO monad" 将值包装在一种 "black box" 中,因此使用 IO monad 的 any 函数仍然是纯函数。好的,好的,但是 IO 操作实际发生在哪里?

这是否意味着 Monad 本身不是纯函数式的?或者说IO操作是用C实现的,"embedded"在Haskell编译器里面?

我可以在纯 Haskell 中使用或不使用 Monad 来编写将执行 IO 操作的东西吗?如果不是,如果在语言本身内部不可能,这种能力从何而来?如果它是 Haskell 编译器内部的 embedded/linked 到 C 代码块,IO Monad 最终会调用它来执行 "dirty job" 吗?

作为序言,它不是 "the IO Monad",尽管很多 poorly-written 介绍都这么说。只是"the IO type"。 monad 没有什么神奇之处。 Haskell 的 Monad class 是一件非常无聊的事情——它只是不熟悉,比大多数语言可以支持的更抽象。你永远不会看到任何人调用 IO "the IO Alternative,",即使 IO 实现了 Alternative。过分关注 Monad 只会妨碍学习。

在纯语言中,原则性处理效果(不是side-effects!)的概念魔法是 IO 类型的存在。是真品。这不是一些标志说 "This is impure!"。它是完整的 Haskell 类型 * -> *,就像 Maybe[]。接受 IO 值作为参数的类型签名,如 IO a -> IO (Maybe a),是有意义的。带有嵌套 IO 的类型签名很有意义,例如 IO (IO a).

所以如果是实型,就一定有具体的含义。 Maybe a 作为类型表示类型 a 的 possibly-missing 值。 [a] 表示 0 个或多个 a 类型的值。 IO a 表示产生 a.

类型值的一系列效果

请注意,IO 的全部目的是表示一系列效果。正如我上面所说,它们不是副作用。它们不能隐藏在程序的 innocuous-looking 叶中,并在其他代码背后神秘地更改内容。相反,效果是通过 IO 值这一事实而明确指出的。这就是为什么人们试图使用 IO 类型来最小化程序的一部分。你在那里做的越少,远距离的怪异动作干扰你的程序的方式就越少。

至于你的问题的主旨,那么 - 一个完整的 Haskell 程序是一个名为 mainIO 值,以及它使用的定义集合。编译器在生成代码时,会插入一个明确的 non-Haskell 代码块,该代码块实际运行 IO 值中的效果序列。从某种意义上说,这就是 Simon Peyton Jones(GHC long-time 的作者之一)在他的演讲 Haskell is useless.

中所表达的意思。

的确,任何实际执行IO 操作的东西都不能保持概念上的纯粹。 (还有那个运行 IO 操作的非常不纯的函数暴露在 Haskell 语言中。我不会多说它是为了支持外部函数接口而添加的,使用不当会严重破坏你的程序。)但是 Haskell 的要点是为效果系统提供一个有原则的接口,并隐藏无原则的位。它以一种在实践中非常有用的方式做到这一点。