为什么这个表达式有一个有效的类型?
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
表示类型 a
和 b
相同。结合这些给出类型
(Num a, Enum a, Num [a]) => [a]
这种 idiomatic/applicative 提升模式以 Data.Monoid.Ap
形式存在,其中 Ap [] 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]
列表的行为是 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]
在 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
表示类型 a
和 b
相同。结合这些给出类型
(Num a, Enum a, Num [a]) => [a]
这种 idiomatic/applicative 提升模式以 Data.Monoid.Ap
形式存在,其中 Ap [] 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]
列表的行为是 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]