4

我在他们的wiki上阅读了Haskell中IO monad的更深层次的工作, 我遇到了这段代码

main = do a <- ask "What is your name?"
      b <- ask "How old are you?"
      return ()
ask s = do putStr s
       readLn

这对我来说很有意义。ask 函数应该打印出给它的字符串并返回可以传递给 a 或 b 的行。

但是将其加载到 GHCi 中我遇到了问题。告诉我没有使用 ask 读取的实例,并且我可以导入 GHC.Read。那不应该是必要的。这段代码在 Haskell.org 上,所以我认为它应该可以工作。语言中的某些内容是否发生了变化,或者我是否缺少一些重要的理解?

4

2 回答 2

7

如果您只使用 ask 函数(没有有问题的 main)创建一个文件并将其加载到 ghci,您将能够看到 ask 的类型是

ask :: (Read a) => String -> IO a

意味着它在返回类型中是多态的。

问题是当你这样做时

a <- ask "What is your name"

编译器需要知道什么是类型,a以便它可以为您从输入中读取的行使用正确的反序列化函数。但是a没有在其他任何地方使用,也没有类型签名,因此类型推断无法推断a. 编译器放弃并为您提供“歧义类型”消息。

有两种主要方法可以解决此问题:

  1. 使 ask 函数始终返回相同的类型。您可以通过添加特定签名来执行此操作

    ask :: String -> IO String
    

    或者通过将 更改readLn为类似getLine.

  2. 在使用多态函数的地方添加类型签名。您可以向 ask 调用本身添加类型签名:

    a <- ask "What is your name" :: IO String
    

    或者您可以直接将其添加到变量中

    (a :: String) <- ask "What is your name"
    

    但是,默认的 Haskell 语法不允许第二个选项。您需要通过将以下注释添加为文件的第一行来启用 ScopedTypeVariables 扩展

    {-# LANGUAGE ScopedTypeVariables #-}
    
于 2013-03-09T03:28:24.153 回答
4

我对代码做了两次更改。

  1. 修复缩进——记住haskell是“空间敏感的”,所以请确保代码看起来正确对齐
  2. 显式类型签名。这有点棘手。但是根据经验,当您期望的代码不起作用时。尝试使用类型注释您的代码,如下所示。你会明白为什么会这样。

这是修改后的代码:

main = do
  a <- askString "What is your name?"
  b <- askOther "How old are you?"

  putStrLn ""
  putStrLn "Name and age"
  putStrLn (a :: String)
  print (b :: Int)
  return ()

askString s = do
  putStrLn s
  getLine

askOther s = do
  putStrLn s
  readLn

编辑:对不起,代码现在实际编译。同样,随着您的 haskell 成熟,您会明白为什么 askString 和 askOther 看起来确实不同。这是一个示例运行:

$ runghc Hello.hs
What is your name?
Arash
How old are you?
22

Name and age
Arash
22
于 2013-03-09T03:21:49.590 回答