6

我是 Haskell 初学者,有一个奇怪的问题。到目前为止一切都很好,我已经能够正常使用 Prelude 读取功能。现在突然间我必须不断声明它的类型才能使用它。

我总是必须声明这个或类似的东西才能使用它。

let r = read::String-> Int

我曾尝试重新启动 ghci,以为我不小心超载了读取,但每当我尝试正常使用它时

read "456"

我收到以下错误

No instance for (Read a0) arising from a use of `read'
The type variable `a0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
  instance Read () -- Defined in `GHC.Read'
  instance (Read a, Read b) => Read (a, b) -- Defined in `GHC.Read'
  instance (Read a, Read b, Read c) => Read (a, b, c)
    -- Defined in `GHC.Read'
  ...plus 25 others
In the expression: read "456"
In an equation for `it': it = read "456"

有没有人有任何想法可能导致这种情况以及如何解决它?

4

2 回答 2

12

首先,要解决您的问题,您可以通过read以下几种方式之一指定您期望的结果:

首先,通过指定整个计算结果的类型:

read "456" :: Int

或者通过指定函数的类型读取:

(read :: String -> Int) "456"

为了解释为什么会发生这种情况,问题read在于其结果类型是多态的。也就是说,定义了许多不同的read函数,它们被定义为 typeclass 的一部分Read。该错误为您解释了原因:

Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
  instance Read () -- Defined in `GHC.Read'
  instance (Read a, Read b) => Read (a, b) -- Defined in `GHC.Read'
  instance (Read a, Read b, Read c) => Read (a, b, c)
    -- Defined in `GHC.Read'
  ...plus 25 others

typeclassRead是为很多很多类型定义的,它表示错误中定义了 28 个实例。它是为 Int、Integer、Double、String 和无数其他定义的。因此,为了让编译器知道要使用哪个类型类的实例,它需要能够从read您指定的类型中推断出来。如果你给它:: String -> Int,编译器可以计算出来。

为了理解它是如何工作的,让我们看一下类型类的一部分Read

class Read a where
  read :: String -> a

因此,对于给定的类型aRead a必须定义 的实例才能使函数read返回类型的值a。因此Int,对于 的实例有一个定义Read Int,并且确实存在于GHC-Read中。该实例定义了如何read工作。不幸的是,该实例相当迟钝,并且依赖于其他功能和其他类似的东西,但为了完整起见,这里是:

instance Read Int where
  readPrec     = readNumber convertInt
  readListPrec = readListPrecDefault
  readList     = readListDefault

但是,要意识到的重要一点是,这个实例意味着read它将适用于Int. 但不久之后,就有了这样一个例子:

instance Read Double where
  readPrec     = readNumber convertFrac
  readListPrec = readListPrecDefault
  readList     = readListDefault

哦亲爱的。所以read适用于IntDouble

Haskell 语言确保编译器通常会尝试推断值的唯一类型,否则会失败。有一些奇怪的例外,GHC 中的语言有一些扩展,但你必须记住,Haskell 试图阻止你在脚下开枪。如果您没有以某种方式指定您期望的类型read,那么编译器可以推断出任何类型,但这可能无效。您不希望您的代码突然开始读取浮点数Double或任意精度Integer,而您之前希望读取的是 an Int,对吧?

在这种情况下,不指定类型只会导致编译器放弃,它无法明确回答您问它的问题,即“Read我使用哪个实例?” 而且 Haskell 编译器不喜欢猜测。防止它猜测的一种方法是以以后明确的方式使用值,或者定义read明确使用的函数。例如,可以在您的代码中使用此函数以避免键入:: String -> Int

readInt :: String -> Int
readInt = read

编译器可以找出Read它需要在那里使用的实例,如果你这样做,在你的代码中:

readInt "456"

编译器会知道readInt有 type String -> Int,因此readInt "456"有 type Int。毫不含糊,就像 Haskell 喜欢的那样。

附录

GHCi 中定义事物的语法有点不同,所以如果你只使用 GHCi 来玩 Haskell,我建议你切换到加载 .hs 文件并使用:r命令重新加载它们。GHCi 的一个问题是您必须用“let”定义所有内容,当您开始编写程序时,这会开始感到奇怪,而顶级定义不需要。另一个问题是单态限制,坦率地说,这有点奇怪和古怪,不值得在这里详细介绍。

无论如何,在 GHCi 中定义readInt上述语法很简单,尽管有两种方法(等效)。你已经知道一个,就是这样做:

let readInt = read :: String -> Int

另一种方法是分别定义函数及其类型,这更类似于在常规 .hs 文件中的完成方式:

let readInt :: String -> Int; readInt = read

惯用的 Haskell 也不是,但那是因为 GHCi 对编写代码施加了古怪的限制,而且多行输入很奇怪。你可以这样做:

:{
let readInt :: String -> Int;
    readInt = read
:}

这将使您接近惯用的 Haskell 定义。但是,如果您将其放入 .hs 文件并使用:load或指定带有 .hs 的文件,GHCi 将正确编译我的初始示例ghci ./somefile.hs

于 2013-07-17T21:25:39.660 回答
9

当您键入

read "456"

在提示符下,ghci 没有任何信息可以找出您想要的类型。你想要一个Int, a Bool, (), String, ...

你需要告诉它,

read "456" :: Int

让它知道。

在实际程序中,通常有上下文确定所需的类型,然后可以推断出来,您不需要手动提供类型。在提示符下,没有上下文可以帮助推理。

于 2013-07-17T21:06:38.610 回答