据我所知,GHC可以将具有默认多态类型Num a => a
的任何数字文字转换为具有Num
实例的任何类型。我想知道这是否正确,并了解一些基本机制。
为了对此进行探讨,我编写了一个名为MySum
的数据类型,该数据类型从Data.Monoid复制了Sum
的功能(部分)。最重要的部分是它包含instance Num a => Num (MySum a)
。
注意-这恰好是我的问题开始的地方。 Monoid并不特别相关。我已经在该问题的底部添加了部分代码,以防万一对于参考内容的答案很有用。
在以下情况下,GHCi似乎很乐意遵守“ v :: MySum t”形式的输入:
v是Num a => a
类型的多态值
t是Num
据我所知,唯一与类型Num a => a
兼容的数字文字是看起来像整数的数字文字。总是这样吗?似乎暗示一个值可以在Num下完全实例化为任何类型。如果这是正确的话,那么在给定5 :: MySum Int
中的函数fromInteger
的情况下,我了解类似Num
的工作方式。
说了这么多,我不知道是怎么回事:
*Main Data.Monoid> 5 :: Fractional a => MySum a
MySum {getMySum = 5.0}
如果有可能以一种对新手友好的方式进行解释,我将不胜感激。
实例Num a => Num (MySum a)
,如所承诺的:
import Control.Applicative
newtype MySum a = MySum {getMySum :: a}
deriving Show
instance Functor MySum where
fmap f (MySum a) = MySum (f a)
instance Applicative MySum where
pure = MySum
(MySum f) <*> (MySum a) = MySum (f a)
instance Num a => Num (MySum a) where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
negate = fmap negate
abs = fmap abs
signum = fmap signum
fromInteger = pure . fromInteger
答案 0 :(得分:4)
您已经发现,整数文字5
amounts to:
fromInteger 5
由于fromInteger
的类型为Num a => Integer -> a
,因此您可以将5
实例化到您选择的Num
实例,例如Int
,{{1 }},Double
或其他任何内容。特别是,假设MySum Double
是Fractional
的子类,并且您编写了一个Num
实例,则Num a => Num (MySum a)
可以正常工作:
5 :: Fractional a => MySum a
似乎暗示一个值可以在Num下完全实例化为任何类型。
这里的东西有些微妙。整数值可以转换为5 :: Fractional a => MySum a
fromInteger 5 :: Fractional a => MySum a
(pure . fromInteger) 5 :: Fractional a => MySum a
MySum (fromInteger 5 :: Fractional a => a)
下的任何类型(通过Num
,通常为fromIntegral
)。我们可以将fromInteger
之类的整数文字实例化为5
下的任何内容,因为GHC通过将其转换为Num
来为我们处理转换。但是,我们无法将单态值fromInteger 5 :: Num a => a
实例化为5 :: Integer
,也不能将Double
实例化为非5 :: Integral a => a
类型,例如Integral
。在这两种情况下,类型注释进一步限制了类型,因此,如果需要Double
,则必须显式执行转换。
答案 1 :(得分:3)
您大体上是正确的:整数文字5
与fromInteger (5 :: Integer)
等效,因此类型为Num a => a
;浮点文字5.0
等效于fromRational (5.0 :: Rational)
,类型为Fractional a => a
。这确实可以解释5 :: MySum Int
。 5 :: Fractional a => MySum a
并不那么棘手。根据上述规则,它扩展为:
fromInteger (5 :: Integer) :: Fractional a => MySum a
fromInteger
的类型为Num b => Integer -> b
。因此,对于上述表达式进行类型检查,GHC必须将b
与MySum a
统一。因此,根据Num (MySum a)
,GHC现在必须解决Fractional a
。 Num (MySum a)
由您的实例解决,产生约束Num a
。 Num
是Fractional
的超类,因此对Fractional a
的任何解决方案也将是Num a
的解决方案。所以一切都检查完了。
不过,您可能想知道,如果5
在此处通过fromInteger
,为什么在GHCi中以MySum
结尾的值看起来像Double
?这是因为在类型检查之后,Fractional a => MySum a
仍然是模棱两可的-当GHCi打印该值时,它实际上需要选择一个a
才能选择合适的Fractional
实例,所有。如果我们不处理数字,那么我们可能最终会在a
中抱怨GHC。
但是有一个特殊情况in the Haskell standard。简短的概述是,如果您遇到上述仅涉及数字类型类的歧义问题,Haskell会明智地选择Integer
或Double
作为歧义类型,并使用第一个类型检查。在这种情况下,即为Double
。如果您想了解更多有关此功能的信息,this blog post会在激励和阐述标准的内容方面做得不错。