了解"let"表达式中IO()的类型



给定:

λ: let f = putStrLn "foo" in 42
42

f的类型是什么?为什么"foo"在显示42的结果之前没有打印出来?

最后,为什么以下不起作用?

λ: :t f
<interactive>:1:1: Not in scope: ‘f’

f的类型是什么?

正如您正确识别的那样,IO ()可以被认为是一个不返回任何有用信息的IO操作(())

为什么在显示42的结果之前不打印"foo"?

Haskell被延迟求值,但在这种情况下,即使是seq也是不够的。只有当表达式返回IO操作时,才会在REPL中执行IO操作。IO操作只有在main返回的情况下才会在程序中执行。然而,有一些方法可以绕过这一限制。

最后,为什么以下不起作用?

Haskell的let命名表达式范围内的值,因此在对表达式求值后,f将超出范围。

let f = ...只是定义了f,并不"运行"任何内容。它与命令式编程中一个新函数的定义有点相似。

您的完整代码let f = putStrLn "foo" in 42可以松散地翻译为

{
function f() {
print("foo");
}
return 42;
}

你不会指望上面会打印出任何东西,对吧?

相比之下,let f = putStrLn "foo" in do f; f; return 42与相似

{
function f() {
print("foo");
}
f();
f();
return 42;
}

信件并不完美,但希望你能明白。

f的类型将为IO ()

"foo"未打印,因为f未"绑定"到现实世界。(我不能说这是一个友好的解释。如果这听起来很无稽之谈,你可能想参考一些教程来了解Monad和懒惰评估的想法)。

let name = value in (scope)使该值在范围内可用,但不在范围外,因此:t不会在ghci的顶级范围内找到它。

不带inlet可用于:t(此代码仅在ghci中有效):

> let f = putStrLn "foo"
> :t f
f :: IO ()

这里有两件事。

首先,考虑

let x = sum [1..1000000] in 42

Haskell是懒惰的。由于我们实际上没有对x做任何事情,所以它永远不会被计算出来。(这也不错,因为它会稍微慢一点。)事实上,如果你编译它,编译器会发现x从未被使用过,并将其删除(即,不为它生成任何编译代码)。

其次,调用putStrLn实际上并没有打印任何内容。相反,它返回IO (),您可以将其视为一种"I/O命令对象"。仅仅拥有一个命令对象与执行it不同。根据设计,"执行"I/O命令对象的唯一方法是从main返回它。至少,它是在一个完整的程序中;GHCi有一个有用的功能,如果您输入一个返回I/O命令对象的表达式,GHCi将为您执行它。

您的表达式返回42;同样,f没有被使用,所以它没有任何作用。

正如chi正确指出的那样,这有点像声明一个本地(零参数)函数,但从不调用它。你不会期望看到任何输出。

你也可以做一些类似的事情

actions = [print 5, print 6, print 7, print 8]

这将创建一个I/O命令对象列表。但是,它不会执行任何一个。

通常,当您编写一个执行I/O的函数时,它是一个do块,将所有内容链接到一个巨大的I/O命令对象中,并将其返回给调用者。在这种情况下,您不需要真正理解定义命令对象和执行命令对象之间的区别。但区别仍然存在。

使用具有显式运行函数的monad可能更容易看到这一点。例如,runST获取一个ST命令对象,运行它,并返回答案。但是(比如)newSTVar本身除了构造ST命令之外什么也不做;你必须runST,在任何事情真正"发生"之前。

相关内容

最新更新