toFloat :: (Floating a) => String -> a
toFloat s = read s :: Float
main = print (toFloat "1")
给我错误:
Could not deduce (a ~ Float)
from the context (Floating a)
我确定我遗漏了一些基本的东西,但似乎我的toFloat应该总是返回一个Float而Float应该意味着浮动。
答案 0 :(得分:18)
类型签名承诺结果将是调用者想要的Floating
类的任何实例。实现说“知道什么?没关系它可以是任何类型的承诺 - 让我们只做一个Float
”。
然后编译器出现并说“哇!你没有回复你所承诺的类型。”除了它努力使您的类型签名和您的实现匹配。它对自己说“如果这种方式受到限制,a
始终与Float
相同,这就是正确的。”它真的想找到一种代码正确的方法。那么,编写这样一个约束的方法是使用类型相等运算符~
。 (a ~ Float)
的约束意味着“a
与Float
的类型相同”。因此编译器会检查您在类型签名中提供的上下文,但无法找到该约束。并且它使您的类型签名和实现无法协同工作,放弃并报告错误。
不幸的是,它报告的错误有点不透明,因为它为尝试使代码工作付出了多少努力。只是最微小的变化,添加一个小约束,就会使它成为正确的。因此它报告说不存在该约束。但它没有报告为什么它正在寻找这种约束,如果你以前没有看过它,整个事情有点不清楚。
答案 1 :(得分:8)
这个问题或与它非常相似的问题会定期提出。 (这不是抱怨,我只是指出你不是唯一被这个混淆的人。)
在OO语言中,您可以说“此函数返回实现X的内容”。然后函数可以返回它返回的感觉,只要它实际上实现了X.
Haskell并不像那样。如果你说“这个函数返回一些实现X的东西”,那么该函数必须能够产生任何可能实现X的类型!
关键区别在于:在OO语言中,函数决定返回的类型(在指定的约束内)。在Haskell中,调用者决定返回什么类型(同样,在规定的约束内)。
一旦你理解了这个关键的区别,剩下的就是不言而喻了。
再次,很多的人似乎误解了这一部分。我们应该在教程和内容中更多地提及它,因为它似乎是一个VFAQ ......
答案 2 :(得分:5)
您说toFloat
可以返回属于Floating
类型类的任何类型,但您将其限制为Float
,这是错误的。您的函数在a
中是多态的,因此您无法返回Floating
的实例,它应该能够处理所有实例。
另外,您可以通过
了解这一点toFloat :: (Read a,Floating a) => String -> a
toFloat s = read s
在ghci
*Main> :t toFloat "12.1"
toFloat "12.1" :: (Floating a, Read a) => a
*Main> :t (toFloat "12.1" :: Float)
(toFloat "12.1" :: Float) :: Float
*Main> :t (toFloat "12.1" :: Double)
(toFloat "12.1" :: Double) :: Double
由于它返回属于类型类Floating
的类型,因此应该能够在应用函数后通过提供显式类型签名将其转换为任何类型(属于Floating
)。
另一方面,当您明确返回Float
时,请记住您的情况,现在您不能仅仅说我期望Double
来自此函数,因为如果没有显式转换,这种情况就不会发生。
另一种了解你的假设是多么可怕的方法是考虑函数read
read :: Read a => String -> a
现在根据您的说法,您可以回复说Int
,因为Int
有Read
的实例。现在您可以了解如果您执行类似
read "12" + (1.2 :: Double)
答案 3 :(得分:1)
这很简单:
-- The simplest.
toFloat :: String -> Float
toFloat = read
-- More generalized.
toFloat' :: (Floating a, Read a) => String -> a
toFloat' = read