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