理解`let`表达式中的IO()类型

时间:2016-05-27 03:33:21

标签: haskell

假设:

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

什么是f的类型?为什么在显示"foo"

的结果之前不会打印42

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

λ: :t f

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

4 个答案:

答案 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