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 应该暗示 Floating。
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 应该暗示 Floating。
类型签名承诺结果将是Floating
调用者想要的类的任何实例。实现说“知道吗?不要介意它可以是任何类型的承诺 - 让我们把它变成一个Float
”。
然后编译器出现并说“哇!你没有返回你承诺的类型。” 除了它非常努力地使您的类型签名和您的实现匹配。它对自己说:“如果以某种方式限制它,那么a
它总是与 相同的东西Float
,这将是正确的。” 它真的很想找到一种方法你的代码是正确的。好吧,编写这种约束的方法是使用~
类型相等运算符。的约束(a ~ Float)
意味着“与”a
的类型相同Float
。因此编译器会检查您在类型签名中提供的上下文,但无法找到该约束。它没有办法让你的类型签名和实现一起工作,放弃并报告错误。
不幸的是,它报告的错误有点不透明,因为它付出了多少努力来尝试使您的代码正常工作。只需最微小的更改,添加一点限制,就可以让它变得正确。因此它报告该约束不存在。但它没有报告它为什么要寻找那个约束,如果你以前没有看过它,这会让整个事情变得有点不清楚。
这个问题或与之非常相似的问题会定期被问到。(这不是抱怨,我只是指出您不是唯一对此感到困惑的人。)
在 OO 语言中,您可以说“这个函数返回实现 X 的东西”。然后,该函数可以返回任何它想要返回的东西,只要它确实实现了 X。
Haskell 不是那样工作的。如果你说“这个函数返回了实现 X 的东西”,那么这个函数必须能够产生任何实现 X 的可能类型!
关键区别在于:在 OO 语言中,函数决定返回什么类型(在指定的约束内)。在 Haskell 中,调用者决定返回什么类型(同样,在规定的约束内)。
一旦你理解了这个关键的区别,剩下的就是不言而喻的了。
同样,很多人似乎误解了这部分。我们可能应该在教程和其他东西中更多地提及它,因为它似乎是一个 VFAQ ......
您是说toFloat
可以返回属于Floating
typeclass 的任何类型,但您将其限制为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
由于它返回属于 typeclass 的类型,因此您应该能够通过在应用函数后提供显式类型签名Floating
将其转换为任何类型(属于)。Floating
另一方面,当您现在显式返回时,请记住您的情况,您Float
不能只说我期望Double
这个函数,因为如果没有显式转换,这是不可能发生的。
另一种理解你的假设有多可怕的方法是考虑函数read
read :: Read a => String -> a
现在根据你的说法,你可以只返回 sayInt
一切,因为Int
有一个Read
. 现在你可以理解如果你做类似的事情会发生什么
read "12" + (1.2 :: Double)
这很简单:
-- The simplest.
toFloat :: String -> Float
toFloat = read
-- More generalized.
toFloat' :: (Floating a, Read a) => String -> a
toFloat' = read