假设:
λ: let f = putStrLn "foo" in 42
42
什么是f
的类型?为什么在显示"foo"
42
最后,为什么以下不起作用?
λ: :t f
<interactive>:1:1: Not in scope: ‘f’
答案 0 :(得分:12)
什么是
1.386294 * (2^128 / (2 * 2.3e7)) = 1.02550305123542e+31
的类型?
正如您已正确识别的那样,f
可以被视为IO动作,不会返回任何有用的内容(IO ()
)
为什么在显示
()
的结果之前不会打印“foo”?
Haskell被懒惰地评估,但在这种情况下即使42
也不够。如果表达式返回IO操作,则只会在REPL中执行IO操作。只有seq
返回的IO操作才会在程序中执行。但是,有办法解决这个限制。
最后,为什么以下不起作用?
Haskell的main
在表达式范围内命名一个值,因此在评估表达式后let
超出范围。
答案 1 :(得分:9)
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;
}
通信并不完美,但希望你能得到这个想法。
答案 2 :(得分:7)
f
的类型为IO ()
。
“foo”未打印,因为f
未与现实世界“绑定”。 (我不能说这是一个友好的解释。如果这听起来像废话,你可能想参考一些教程来捕捉Monad和懒惰评估的想法。)
let name = value in (scope)
使值可用,但不在范围之外,因此:t
将无法在ghci的顶级范围内找到它。
let
的 in
可供:t
使用(此代码仅在ghci中有效):
> let f = putStrLn "foo"
> :t f
f :: IO ()
答案 3 :(得分:4)
这里有两件事情。
首先,考虑
let x = sum [1..1000000] in 42
Haskell 懒惰。由于我们实际上没有对x
做任何事情,因此永远不会计算它。 (这也是一样,因为它会有点慢。)的确,如果你编译它,编译器会看到x
从未使用过,并删除它(即,不为它生成任何编译代码)
其次,调用putStrLn
实际上不会打印任何内容。相反,它返回IO ()
,您可以将其视为一种“I / O命令对象”。仅仅拥有一个命令对象与执行它不同。按照设计,“执行”I / O命令对象的唯一方法是从main
返回它。至少,它是一个完整的程序; GHCi有一个有用的功能,如果你输入一个返回I / O命令对象的表达式,GHCi将为你执行它。
你的表达式返回42;再次,f
未被使用,因此它没有做任何事情。
正如chi
正确指出的那样,它有点像声明一个本地(零参数)函数但从不调用它。你不会期望看到任何输出。
您也可以执行类似
的操作actions = [print 5, print 6, print 7, print 8]
这将创建一个I / O命令对象列表。但是,同样,它不会执行任何一个。
通常,当您编写一个执行I / O的函数时,它是一个do-block,它将所有内容链接到一个巨大的I / O命令对象中并将其返回给调用者。在这种情况下,您并不需要理解定义命令对象和执行它之间的区别。但区别仍然存在。
使用具有显式运行功能的monad可能更容易看到这一点。例如,runST
接受ST命令对象,运行它,并返回答案。但是(比如说)newSTVar
本身除了构造ST命令之外什么都不做;在实际发生任何事情之前,你必须runST
。