[英]Typeclass behavior in Haskell
作为Haskell的初学者,我很难理解为什么会失败
-- this works fine of course
f :: Float
f = 1.0
f :: Num a => a
f = 1.0
-- Could not deduce (Fractional a) arising from the literal ‘1.0’
-- from the context: Num a
-- bound by the type signature for:
-- f :: forall a. Num a => a
我的困惑源于他们都有Num的例子。 因此,由于Int,Integers,Doubles等都有Num类型类的实例,为什么我不能在f
填充任何数值?
例如, negate
它有一个签名negate :: Num a => a -> a
将与廉政局的浮动双打等工作
任何见解将不胜感激。
这里的问题是,当你写f :: Num a => a
,这意味着f
必须为所有可能的实例运行a
使得Num a
实例存在。 特别是,这意味着在其他地方写入(f :: Int)
应该可以正常工作,因为instance Num Int
肯定存在。 但是,为f
返回的值是1.0
,它不是整数: 1.0 :: Fractional p => p
。 错误消息基本上是说“知道a
是一个Num
并没有告诉我们a
是一个Fractional
,所以分数字面1.0没有办法让类型为a
”。
想想它的一种方法是, 调用者就可以选择什么a
应该是:这就是为什么这种形式的类型签名被称为普遍量化 。
您可能正在考虑存在量化: f
返回某种Num
,但“调用者”不知道Num
返回了什么。 这通常不如通用量化那么有用,并且在Haskell中有点笨拙,但可以像这样完成:
{-# LANGUAGE ExistentialQuantification #-} -- This is a GHC extension
-- A SomeNum value contains some kind of Num. Can't see what kind from the "outside".
data SomeNum = forall a. Num a => SomeNum a
f :: SomeNum
f = SomeNum 5.0
在Haskell中,像f :: a
这样的签名是(概念上更清晰的) f :: forall a. a
语法f :: forall a. a
f :: forall a. a
,其中forall
很像“类型级lambda”(而不是f :: exists a. a
你可能一直在考虑)。 事实上,如果你看一下GHC Core,你会看到所有这些类型的应用程序都是显式的:每当你使用一个通用量化的函数时,“调用者”显式地传入要用于每个类型变量的类型。
但是,我建议您不要在此阶段尝试使用存在量化:通常有更好/更容易的替代方案。 我只是想解释它,以帮助显示存在与普遍性之间的区别。
为什么我不能在
f
填充任何数值?例如,具有签名
negate :: Num a => a -> a
将适用于Int
的Float
的Double
s等。
正因为如此!
你不能在f
的定义中填充任何数值,因为你不能在negate
的定义中填充任何数值。
假设我们试图定义negate
如下
negate :: Num a => a -> a
negate x = 10.5 - 10.5 - x
这会对Int
工作吗? 也就是说, negate 42 :: Int
? 不,因为10.5
不是Int
。
由于negate
的类型承诺它适用于任何数字类型,包括Int
,但它实际上不适用于Int
,因此承诺被破坏。 静态类型检查因此拒绝该代码。
同样,如果接受了类型检查
f :: Num a => a
f = 10.5
然后所有这些都应该工作: f + 8 :: Int
, f / 2 :: Double
, f - 4 :: Integer
。 但是10.5
不适合Int
(也不适合Integer
)。
这里的问题是f :: Num a => a
允许调用者选择任何数字类型a
。 由于类型允许调用者选择, f
选择,但必须适应调用者做出的任何选择。 因此, f
不能使用仅适用于某些数字类型的代码,而不能使用其他类型的代码。
如果f
仅适用于Fractional
类型( Num
类型的子集),则f
的类型必须向调用者通告其选择仅限于小数类型。 这是通过使用f :: Fractional a => a
来完成的。
如果你只是写
f = 10.5
你回来了f :: Fractional a => a
没有问题。
当你明确声明它的类型为Num a => a
,Haskell必须统一声明的和实际的类型,而它不能,因为Fractional
是Num
的子类 :
>> :i Fractional
class Num a => Fractional a where
-- ^^^ -- Fractional is Num's subclass
...........
fromRational :: Rational -> a
...........
每个Fractional
都是一个Num
,但不是每个Num
都是Fractional
。
fromRational
的类型来自fromRational :: Fractional a => Rational -> a
。
这表明像10.5
这样的浮点文字实际上是从fromRational (10.5 :: Rational)
读取的,就像整个数字文字(如10
被读作fromInteger (10 :: Integer)
。
实际上, Haskell教程中的第10.3节内容如下:
整数(不带小数点)实际上等同于
fromInteger
到数字值作为Integer
。 类似地,浮动数字(带小数点)被视为fromRational
到数字值作为Rational
。 因此,7
具有类型(Num a) => a
,并且7.3
具有类型(Fractional a) => a
。
当你给编译器类型声明一样f :: Num a => a
,你不只是说f
有类型类Num
但是这一切 ,你知道它在这里。 由于您输入的是1.0
而不是1
,因此编译器断定您还需要Fractional
。 你的声明说你没有,所以这不能编译。 如果你说它是Fractional a => a
,它会编译,而Fractional
表示Num
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.