为什么这个表达式有一个有效的类型?

Why does this expression have a valid type?

ghci 中转来转去,我偶然注意到表达式 (*) 1 [1..5] 显然具有有效类型。

:t (*) 1 [1..5]
(*) 1 [1..5] :: (Enum t, Num [t], Num t) => [t]

显然它是一个具有多种类型约束的列表,包括 Num [t],这在我看来是不可能的,就像它应该给出错误一样。

表达式的类型如何?为什么ghci: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 被脱糖为类型 Num t => t

fromInteger (1::Integer)

范围

在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 表示类型 ab 相同。结合这些给出类型

(Num a, Enum a, Num [a]) => [a]

这种 idiomatic/applicative 提升模式以 Data.Monoid.Ap 形式存在,其中 Ap [] a 指定 purefmapliftA2 的提升操作:(+) = 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]

列表的行为是 derived via Ap [] a. You get different applicative behaviour through 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]