这是对IO
单子的建议“解释” 。如果您想认真对待这种“解释”,那么就需要认真对待“现实世界”。不管是否action world
进行投机评估,action
没有任何副作用都是无关紧要的,它的影响(如果有的话)是通过返回已发生这些影响的宇宙的新状态(例如,已发送网络数据包)来处理的。但是,该函数的结果为((),world)
,因此宇宙的新状态为world
。我们不会使用我们可能会在侧面进行推测性评估的新宇宙。宇宙的状态是world
。
您可能很难认真对待这一点。充其量从表面上讲是矛盾的和荒谬的,有很多方法。从这个角度来看,并发尤其是非显而易见的或疯狂的。
“等等,”您说。“ RealWorld
只是一个'令牌'。它实际上不是整个宇宙的状态。” 好的,那么这个“解释”什么也不能解释。然而,作为实施细节,这是GHC建模的方式IO
。1但是,这意味着我们确实具有神奇的“功能”,而这些功能实际上确实具有副作用,并且该模型没有为其含义提供指导。而且,由于这些功能实际上有副作用,因此您提出的关注点是完全正确的。GHC 确实必须尽力确保RealWorld
这些特殊功能没有以改变程序预期行为的方式进行优化。
就个人而言(目前可能已经很明显了),我认为这种“遍及世界”的模式IO
作为一种教学工具,毫无用处且令人困惑。(我不知道它是否对实现有用。对于GHC,我认为它更多是历史产物。)
一种替代方法是将IO
响应请求视为描述请求。有几种方法可以做到这一点。可能最方便的方法是使用免费的monad构造,特别是我们可以使用:
data IO a = Return a | Request OSRequest (OSResponse -> IO a)
有很多方法可以使它更复杂并具有更好的性能,但这已经是一种改进。不需要了解关于现实本质的深刻哲学假设。它所说明的只是IO
一个琐碎的程序Return
,什么都不做,只返回一个值,或者是对操作系统的请求,并带有用于响应的处理程序。OSRequest
可以是这样的:
data OSRequest = OpenFile FilePath | PutStr String | ...
同样,OSResponse
可能类似于:
data OSResponse = Errno Int | OpenSucceeded Handle | ...
(可以做的改进之一是使事情更安全,使您知道不会OpenSucceeded
从PutStr
请求中得到。)此模型IO
描述的是描述被某些系统解释的请求(对于“真实” IO
monad,这是(Haskell运行时本身),然后,该系统可能会调用我们提供的响应处理程序。当然,这也不能表示PutStr "hello world"
应如何处理类似的请求,但也不能假装。它明确表明将其委托给其他系统。这个模型也很准确。现代操作系统中的所有用户程序都需要向操作系统发出请求以执行任何操作。
该模型提供了正确的直觉。例如,许多初学者将诸如<-
运算符之类的东西视为“解包” IO
,或者具有(不幸地得到加强)这样的观点IO String
,比如说是“包含” String
(然后<-
将其取出)的“容器” 。这种请求-响应视图使此观点明显错误。中没有文件句柄OpenFile "foo" (\r -> ...)
。强调这一点的通常比喻是,蛋糕食谱中没有蛋糕(在这种情况下,“发票”可能会更好)。
该模型还可以并发使用。我们可以轻松地为OSRequest
like创建一个构造函数Fork :: (OSResponse -> IO ()) -> OSRequest
,然后运行时可以将这个额外的处理程序产生的请求与它喜欢的普通处理程序进行交织。有了一些技巧,您可以使用此(或相关技术)更直接地对诸如并发之类的事物进行实际建模,而不仅仅是说“我们向OS发出请求,事情就发生了”。IOSpec
库就是这样工作的。
1 Hugs使用了基于连续的实现,IO
该实现与我描述的大致相似,尽管使用的是不透明函数而不是显式数据类型。HBC还使用了基于延续的实现,该实现位于旧的基于请求-响应流的IO之上。NHC(因此也称为YHC)使用了thunk,即IO a = () -> a
虽然虽然()
被称为World
,但它并未进行状态传递。JHC和UHC使用的基本方法与GHC相同。