在什么意义上一元IO 类型是纯的?
从某种意义上说,IO 类型的值是 Standard ML 的一部分抽象命令式代码理想情况下只能由 Haskell 实现的 RTS 处理 - 在 How to Declare an Imperative 中,Philip Wadler 提供了有关如何实现这一点的提示:
(* page 26 *)
type 'a io = unit -> 'a
infix >>=
val >>= : 'a io * ('a -> 'b io) -> 'b io
fun m >>= k = fn () => let
val x = m ()
val y = k x ()
in
y
end
val return : 'a -> 'a io
fun return x = fn () => x
(* page 27 *)
val execute : unit io -> unit
fun execute m = m ()
不过,not everyone 认为这种情况是可以接受的:
[...] 一种基于机器的无状态计算模型,其最
显着特点是状态[意味着]模型和机械之间的差距很大,因此弥合成本很高。 [...]
这在适当的时候也得到了功能的主角们的认可
语言。他们以各种棘手的方式引入了状态(和变量)。
纯粹的功能特性因此受到损害和牺牲。 [...]
Niklaus Wirth.
...Miranda(R) 的任何人?
我将 IO monad 描述为 State monad,其中状态是“现实世界”。
这将是经典的pass-the-planet I/O 模型,Clean 直接使用它:
import StdFile
import StdMisc
import StdString
Start :: *World -> *World
Start w = putString "Hello, world!\n" w
putString :: String *World -> *World
putString str world
# (out, world1) = stdio world
# out1 = fwrites str out
# (_, world2) = fclose out1 world1
= world2
putChar :: Char *World -> *World
putChar c w = putString {c} w
这种 I/O 方法的支持者认为,这使得 I/O 操作变得纯粹,就像引用透明一样。这是为什么呢?
因为它通常是正确的。
来自标准的 Haskell 2010 库模块Data.List:
mapAccumL _ s [] = (s, [])
mapAccumL f s (x:xs) = (s'',y:ys)
where (s', y ) = f s x
(s'',ys) = mapAccumL f s' xs
如果这个习惯用法如此普遍以至于它有特定的定义来支持它,那么它作为 I/O 模型(具有合适的状态类型)的使用就不足为奇了 - 来自@987654328 的第 14-15 页@John Launchbury 和 Simon Peyton Jones:
那么,I/O 操作到底是如何执行的呢?整个节目的意义
由顶级标识符mainIO的值给出:
mainIO :: IO ()
mainIO 是一个 I/O 状态转换器,通过以下方式应用于外部世界状态
操作系统。从语义上讲,它返回了一个新的世界状态,并且
其中体现的变化应用于现实世界。
(...回到 main 被称为 mainIO 的时候。)
最新的Clean Language Report(I/O on the Unique World,第 24 页,共 148 页)更详细:
赋予初始表达式的世界是一个抽象数据结构,一个*World类型的抽象世界
从程序中看到具体的物理世界。抽象世界原则上可以包含
anything 函数式程序在执行期间需要与具体世界交互的内容。世界可以看作一个
状态和世界的修改可以通过定义在世界或世界的一部分上的状态转换函数来实现。经过
要求这些状态转换函数在唯一世界上工作,抽象世界的修改可以直接
在真实的物理世界中实现,不会损失效率,也不会失去参考透明度。
就语义而言,关键点是:要使以 I/O 为中心的程序的更改生效,该程序必须返回最终的世界状态值。
现在考虑这个小的 Clean 程序:
Start :: *World -> *World
Start w = loopX w
loopX :: *World -> *World
loopX w
# w1 = putChar 'x' w
= loopX w1
Obviously 永远不会返回最终的 World 值,因此根本不应该看到 'x'...
另外,难道不能像现实世界的函数那样描述几乎任何非纯函数吗?
是的;这或多或少是 FFI 在 Haskell 2010 中的工作方式。
在我看来,monadic IO 类型内的代码似乎有很多可观察到的副作用。
如果您使用的是 GHC,它不是外观 - 来自
A History of Haskell(第 26 页,共 55 页)作者:Paul Hudak、John Hughes、Simon Peyton Jones 和 Philip Wadler:
当然,GHC 实际上并没有环游世界;相反,它传递一个虚拟的“令牌”,以确保在存在惰性求值的情况下正确排序操作,并将输入和输出作为实际副作用执行!
但这只是一个实现细节:
IO 计算是一个函数,它(逻辑上)获取世界状态,并返回修改后的世界以及返回值。
逻辑不适用于现实世界。
马文·李·明斯基。