为什么Haskell不能通过使用严格求值来执行IO操作?

17

我正在学习Haskell和IO monad。我想知道为什么这个程序不会强制输出"hi"以及"bye":

second a b = b
main = print ((second $! ((print "hi") >>= (\r -> return ()))) "bye")
据我理解,$! 运算符会强制对 second 的第一个参数求值,而>>= 运算符需要运行 print "hi" 才能将其值传递给 \r -> return (),它将在屏幕上打印“hi”。我的推理有什么问题吗?
另外,除了使用不安全的函数之外,是否有任何方法可以证明Haskell不能被欺骗,在“安全”的代码中运行IO操作?

6
顺便提一下,你的代码可以简化为 main = print (print "hi" \seq` "bye")`。请注意,我的翻译保留了原始意思但可能会更通俗易懂。 - Joachim Breitner
7
回答:因为你无法欺骗 Haskell。 - Don Stewart
正如Joachim所指出的那样,($!)使用了seq,这已经让许多开发人员陷入困境,例如参见GHC问题#5129,其中ezyang提出了另一个有问题的seq使用;它提供了更多关于为什么你的代码示例在Haskell 2010中无法工作的细节。 - atravers
2个回答

34
你正在强制执行的表达式是((print "hi") >>= (\r -> return ())),它的类型为IO()。因此它代表了一种IO操作。但是评估这样的东西与运行它非常不同!
评估一个值意味着执行足够的步骤将其转换为所谓的weak head normal form。由于IO是抽象的,因此在这种情况下有点棘手,但可以将IO a视为RealWorld -> (a, RealWorld),然后弱头正常形式是,好吧,一个函数等待被给予RealWorld
运行意味着评估,但也将RealWorld作为参数传递,从而导致IO效果发生。

所有这些并不是特定于 IO;你的困惑和概念同样适用于 a -> b。如果你理解当第二个参数是一个函数时 $! 的作用,那么当它是一个 IO 动作时会发生什么也就明白了。


21
你将评估执行混淆了。强制执行类似于print "hi"这样的表达式时,并不会打印出任何内容。它只是生成一个值(在这种情况下,是类型为IO()的值),该值表示打印某些内容的操作。你可以将print "hi"的值看作是一份打印“hi”的食谱。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接