首先,要解决您的问题,您可以通过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
因此,对于给定的类型a
,Read 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
适用于Int
和Double
!
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
。