(^) 上的类型推断问题
Problems With Type Inference on (^)
所以,我正在尝试编写自己的 Prelude 替代品,并且我已经 (^) 实现了这样的功能:
{-# LANGUAGE RebindableSyntax #-}
class Semigroup s where
infixl 7 *
(*) :: s -> s -> s
class (Semigroup m) => Monoid m where
one :: m
class (Ring a) => Numeric a where
fromIntegral :: (Integral i) => i -> a
fromFloating :: (Floating f) => f -> a
class (EuclideanDomain i, Numeric i, Enum i, Ord i) => Integral i where
toInteger :: i -> Integer
quot :: i -> i -> i
quot a b = let (q,r) = (quotRem a b) in q
rem :: i -> i -> i
rem a b = let (q,r) = (quotRem a b) in r
quotRem :: i -> i -> (i, i)
quotRem a b = let q = quot a b; r = rem a b in (q, r)
-- . . .
infixr 8 ^
(^) :: (Monoid m, Integral i) => m -> i -> m
(^) x i
| i == 0 = one
| True = let (d, m) = (divMod i 2)
rec = (x*x) ^ d in
if m == one then x*rec else rec
(注意这里用的Integral是我定义的,不是Prelude里的,虽然很像,另外one
是一个多态常数,是幺半群运算下的恒等式)
数字类型是幺半群,所以我可以尝试做,比如 2^3,但是类型检查器给了我:
*AlgebraicPrelude> 2^3
<interactive>:16:1: error:
* Could not deduce (Integral i0) arising from a use of `^'
from the context: Numeric m
bound by the inferred type of it :: Numeric m => m
at <interactive>:16:1-3
The type variable `i0' is ambiguous
These potential instances exist:
instance Integral Integer -- Defined at Numbers.hs:190:10
instance Integral Int -- Defined at Numbers.hs:207:10
* In the expression: 2 ^ 3
In an equation for `it': it = 2 ^ 3
<interactive>:16:3: error:
* Could not deduce (Numeric i0) arising from the literal `3'
from the context: Numeric m
bound by the inferred type of it :: Numeric m => m
at <interactive>:16:1-3
The type variable `i0' is ambiguous
These potential instances exist:
instance Numeric Integer -- Defined at Numbers.hs:294:10
instance Numeric Complex -- Defined at Numbers.hs:110:10
instance Numeric Rational -- Defined at Numbers.hs:306:10
...plus four others
(use -fprint-potential-instances to see them all)
* In the second argument of `(^)', namely `3'
In the expression: 2 ^ 3
In an equation for `it': it = 2 ^ 3
我知道这是因为 Int 和 Integer 都是 Integer 类型,但是为什么在普通的 Prelude 中我可以做到这一点呢? :
Prelude> :t (2^)
(2^) :: (Num a, Integral b) => b -> a
Prelude> :t 3
3 :: Num p => p
Prelude> 2^3
8
即使我的部分应用程序的签名看起来相同?
*AlgebraicPrelude> :t (2^)
(2^) :: (Numeric m, Integral i) => i -> m
*AlgebraicPrelude> :t 3
3 :: Numeric a => a
我怎样才能使 2^3 实际上起作用,从而得到 8?
Hindley-Milner 类型系统真的不喜欢必须默认 任何东西。在这样的系统中,您希望类型是适当固定(刚性,骨架)或适当多态,但“这是,比如,一个整数……但如果你愿意,我也可以将它转换为其他东西”,因为许多其他语言并没有真正解决这个问题。
因此,Haskell 违约很糟糕。它没有 first-class 支持,只有一个非常 hacky 的临时硬编码机制,主要处理内置数字类型,但在涉及更多内容时失败。
因此,您应该尽量不要依赖违约。我的意见是 ^
的标准签名是不合理的;更好的签名是
(^) :: Num a => a -> Int -> a
Int
可能是有争议的——当然Integer
在某种意义上会更安全;但是,指数 太大而无法放入 Int
通常意味着结果将完全超出范围,无法通过迭代乘法计算;所以这样很好地表达了意图。它为您只写 x^2
或类似的极其常见的情况提供了最佳性能,这是您绝对不想在指数中添加额外签名的情况。
在极少数情况下,你有一个具体的例子。 Integer
数字并想在指数中使用它,您始终可以显式地推入 fromIntegral
。这不是很好,但不方便。
作为一般规则,我尝试避免†任何比结果更具多态性的函数参数。 Haskell 的多态性最适合“向后”,即与动态语言相反的方式:调用者请求结果应该是什么类型,编译器从中计算出参数应该是什么。这几乎总是有效,因为一旦在主程序中以某种方式使用结果,整个计算中的类型就必须链接到树结构。
OTOH,推断结果的类型通常是有问题的:参数可能是可选的,可能本身仅链接到结果,或者作为多态常量给出,如 Haskell 数字文字。因此,如果 i
没有出现在 ^
的结果中,也不要让 in 出现在参数中。
†“避免”并不意味着我从不写它们,除非有充分的理由,否则我不会这样做。
所以,我正在尝试编写自己的 Prelude 替代品,并且我已经 (^) 实现了这样的功能:
{-# LANGUAGE RebindableSyntax #-}
class Semigroup s where
infixl 7 *
(*) :: s -> s -> s
class (Semigroup m) => Monoid m where
one :: m
class (Ring a) => Numeric a where
fromIntegral :: (Integral i) => i -> a
fromFloating :: (Floating f) => f -> a
class (EuclideanDomain i, Numeric i, Enum i, Ord i) => Integral i where
toInteger :: i -> Integer
quot :: i -> i -> i
quot a b = let (q,r) = (quotRem a b) in q
rem :: i -> i -> i
rem a b = let (q,r) = (quotRem a b) in r
quotRem :: i -> i -> (i, i)
quotRem a b = let q = quot a b; r = rem a b in (q, r)
-- . . .
infixr 8 ^
(^) :: (Monoid m, Integral i) => m -> i -> m
(^) x i
| i == 0 = one
| True = let (d, m) = (divMod i 2)
rec = (x*x) ^ d in
if m == one then x*rec else rec
(注意这里用的Integral是我定义的,不是Prelude里的,虽然很像,另外one
是一个多态常数,是幺半群运算下的恒等式)
数字类型是幺半群,所以我可以尝试做,比如 2^3,但是类型检查器给了我:
*AlgebraicPrelude> 2^3
<interactive>:16:1: error:
* Could not deduce (Integral i0) arising from a use of `^'
from the context: Numeric m
bound by the inferred type of it :: Numeric m => m
at <interactive>:16:1-3
The type variable `i0' is ambiguous
These potential instances exist:
instance Integral Integer -- Defined at Numbers.hs:190:10
instance Integral Int -- Defined at Numbers.hs:207:10
* In the expression: 2 ^ 3
In an equation for `it': it = 2 ^ 3
<interactive>:16:3: error:
* Could not deduce (Numeric i0) arising from the literal `3'
from the context: Numeric m
bound by the inferred type of it :: Numeric m => m
at <interactive>:16:1-3
The type variable `i0' is ambiguous
These potential instances exist:
instance Numeric Integer -- Defined at Numbers.hs:294:10
instance Numeric Complex -- Defined at Numbers.hs:110:10
instance Numeric Rational -- Defined at Numbers.hs:306:10
...plus four others
(use -fprint-potential-instances to see them all)
* In the second argument of `(^)', namely `3'
In the expression: 2 ^ 3
In an equation for `it': it = 2 ^ 3
我知道这是因为 Int 和 Integer 都是 Integer 类型,但是为什么在普通的 Prelude 中我可以做到这一点呢? :
Prelude> :t (2^)
(2^) :: (Num a, Integral b) => b -> a
Prelude> :t 3
3 :: Num p => p
Prelude> 2^3
8
即使我的部分应用程序的签名看起来相同?
*AlgebraicPrelude> :t (2^)
(2^) :: (Numeric m, Integral i) => i -> m
*AlgebraicPrelude> :t 3
3 :: Numeric a => a
我怎样才能使 2^3 实际上起作用,从而得到 8?
Hindley-Milner 类型系统真的不喜欢必须默认 任何东西。在这样的系统中,您希望类型是适当固定(刚性,骨架)或适当多态,但“这是,比如,一个整数……但如果你愿意,我也可以将它转换为其他东西”,因为许多其他语言并没有真正解决这个问题。
因此,Haskell 违约很糟糕。它没有 first-class 支持,只有一个非常 hacky 的临时硬编码机制,主要处理内置数字类型,但在涉及更多内容时失败。
因此,您应该尽量不要依赖违约。我的意见是 ^
的标准签名是不合理的;更好的签名是
(^) :: Num a => a -> Int -> a
Int
可能是有争议的——当然Integer
在某种意义上会更安全;但是,指数 太大而无法放入 Int
通常意味着结果将完全超出范围,无法通过迭代乘法计算;所以这样很好地表达了意图。它为您只写 x^2
或类似的极其常见的情况提供了最佳性能,这是您绝对不想在指数中添加额外签名的情况。
在极少数情况下,你有一个具体的例子。 Integer
数字并想在指数中使用它,您始终可以显式地推入 fromIntegral
。这不是很好,但不方便。
作为一般规则,我尝试避免†任何比结果更具多态性的函数参数。 Haskell 的多态性最适合“向后”,即与动态语言相反的方式:调用者请求结果应该是什么类型,编译器从中计算出参数应该是什么。这几乎总是有效,因为一旦在主程序中以某种方式使用结果,整个计算中的类型就必须链接到树结构。
OTOH,推断结果的类型通常是有问题的:参数可能是可选的,可能本身仅链接到结果,或者作为多态常量给出,如 Haskell 数字文字。因此,如果 i
没有出现在 ^
的结果中,也不要让 in 出现在参数中。
†“避免”并不意味着我从不写它们,除非有充分的理由,否则我不会这样做。