如果Haskell懒惰,为什么在以下两种情况下都会评估getLine
?由于懒惰,我希望在fa
的情况下不会评估getLine
,因为它的结果随后不会被使用:
let fa = do {
x <- getLine;
return "hello"
}
let fb = do {
x <- getLine;
return $ x
}
(我在GHCi中测试了这两种情况)
谢谢
它的结果被使用,只是没有按照你必然期望的方式使用。 这种脱糖
fa = getLine >>= (x -> return "hello")
所以getLine
的结果仍然传递给函数x -> return "hello"
。 Monads 本质上是关于一起排序动作(除其他外);即使以后不使用结果,该排序仍然会发生。 如果不是这样,那么
main = do
print "Hello"
print "World"
不会作为程序做任何事情,因为对print
的两个调用的结果都没有被使用。
恭喜你,你刚刚发现了为什么Haskell是一种纯粹的函数式语言!
结果† 的 getLine
不是字符串。这是一个碰巧"产生"字符串的 IO 操作。该字符串确实没有被评估,但动作本身是(因为它出现在绑定到 main
的do
块中),就副作用
†真的只是getLine
的价值。这不是一个函数,所以它实际上没有结果。
现在要小心... ;-)
getLine
的结果不是字符串,而是"I/O 命令对象"(如果您愿意的话)。代码实际上脱糖为
getLine >>= ( x -> return "hello")
>>=
运算符从现有 I/O 命令对象和函数中构造一个新的 I/O 命令对象...好吧,这有点让你思考。重要的是,I/O 操作被执行(因为实现了 >>=
for IO
),但其结果不一定会被评估(因为懒惰)。
那么让我们看看IO
monad 的实现......呃,其实你知道吗?我们不要。(它是深奥的魔术,硬连接到编译器中,因此它是特定于实现的。但这种现象绝不是IO
独有的。让我们看一下Maybe
的monad:
instance Monad Maybe where
mx >>= f =
case mx of
Nothing -> Nothing
Just x -> f x
return x = Just x
所以如果我做类似的事情
do
x <- foobar
return "hello"
x
会得到评估吗?让我们看看。它脱糖为:
foobar >>= ( x -> return "hello")
那么这变成了
case foobar of
Nothing -> Nothing
Just x -> Just "hello"
如您所见,foobar
显然将被评估,因为我们需要知道结果是Nothing
还是Just
。但是不会评估实际x
,因为没有任何东西会查看它。
这与length
评估列表节点的方式相同,但不是它们指向的列表元素。