在 中敲打ghci
,我碰巧注意到这个表达式(*) 1 [1..5]
显然有一个有效的类型。
:t (*) 1 [1..5]
(*) 1 [1..5] :: (Enum t, Num [t], Num t) => [t]
显然它是一个包含多个类型约束的列表,包括Num [t]
对我来说看起来不可能的,就像它应该给出一个错误一样。
这是表达式的类型?为什么ghci
's:t
命令在这里没有给出错误?
在 中敲打ghci
,我碰巧注意到这个表达式(*) 1 [1..5]
显然有一个有效的类型。
:t (*) 1 [1..5]
(*) 1 [1..5] :: (Enum t, Num [t], Num t) => [t]
显然它是一个包含多个类型约束的列表,包括Num [t]
对我来说看起来不可能的,就像它应该给出一个错误一样。
这是表达式的类型?为什么ghci
's:t
命令在这里没有给出错误?
Num [t]
不仅可能,而且很容易:
import Control.Applicative
liftA0 = pure -- hobgoblins, simple minds, etc.
liftA1 = fmap
instance Num t => Num [t] where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
negate = liftA1 negate
abs = liftA1 abs
signum = liftA1 signum
fromInteger n = liftA0 (fromInteger n)
因此,如果 GHC 产生错误而不是推断您的表达式可以用适当的实例很好地键入,那将是可怕的。
当然,用真实的代码编写这个实例也是很糟糕的,但 GHC 不应该像我们人类那样对代码做出判断。
让我们看看这些约束是如何解释类型的。
在 Haskell 中,文字数字被替换为调用fromInteger
(或者fromRational
如果它有小数点或“e”)。这样,一个人可以写 '1' 并将其设置为 float 或 double 或 int 或其他。的类型fromInteger
是
fromInteger :: Num a => a
所以1
被脱糖到fromInteger (1::Integer)
哪个有类型Num t => t
在 Haskell 中,语法[a..b]
被转换为调用enumFromTo a b
,类型为enumFromTo :: Enum a => a -> a -> [a]
. 把这些放在一起,我们得到
[1..5] == enumFromTo (fromInteger 1) (fromInteger 5) :: (Enum a, Num a) => [a]
现在的类型(*)
是Num b => b -> b -> b
,所以我们将这些组合在一起得到:
(Num t,
Num a,
Enum a,
Num b,
t~b,
[a]~b) => b
请注意,这a~b
意味着类型a
和b
是相同的。结合这些给出了类型
(Num a, Enum a, Num [a]) => [a]
这种惯用/应用提升模式存在于Data.Monoid.Ap
whereAp [] a
指定提升操作的地方pure
,fmap
和liftA2
: (+) = liftA2 (+)
:
>> :set -XDerivingVia
>> :set -XStandaloneDeriving
>>
>> import Data.Monoid (Ap(..))
>>
>> deriving via Ap [] a instance Num a => Num [a]
>>
>> 1 * [1..5]
[1,2,3,4,5]
>> [100,200] * [1..5]
[100,200,300,400,500,200,400,600,800,1000]
列表的行为是通过Ap [] a
. 您通过以下方式获得不同的应用程序行为ZipList
>> import Control.Applicative (ZipList(..))
>>
>> deriving via Ap ZipList a instance Num a => Num [a]
>>
>> 1 * [1..5]
[1,2,3,4,5]
>> [100,200] * [1..5]
[100,400]