(^)

时间:2017-10-31 16:08:51

标签: haskell type-inference exponentiation monoids hindley-milner

所以,我正在尝试为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

(注意这里使用的积分是我定义的积分,而不是Prelude中的积分,尽管它是相似的。另外,one是一个多态常量,它是幺半群运算下的同一性。)

数字类型是幺半群,所以我可以尝试做,比如2 ^ 3,但是typechecker给了我:

*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都是积分类型,但是为什么在正常的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?

1 个答案:

答案 0 :(得分:1)

Hindley-Milner型系统并不像默认任何东西。在这样的系统中,您希望类型正确修复(刚性,skolem)或正确多态,但“这就像整数一样......但是如果你愿意的话,我也可以将它转化为其他东西,“因为许多其他语言并没有真正成功。

因此,Haskell糟透了。它没有一流的支持,只有一个非常hacky ad-hoc,硬编码机制,主要处理内置数字类型,但在任​​何更多涉及的内容都失败。

因此,您应该尽量不依赖违约。我的观点是^的标准签名是不合理的;更好的签名将是

(^) :: Num a => a -> Int -> a

Int可能存在争议 - 当然Integer在某种意义上会更安全;然而,指数太大而不适合Int通常意味着结果将完全偏离规模,并且不可能通过迭代乘法计算;所以这种表达方式很好。并且它为您只写x^2或类似的非常常见的情况提供了最佳性能,这是您绝对不希望在指数中添加额外签名的内容。

在较少的情况下,你有一个具体的例如Integer号码并希望在指数中使用它,您可以随时使用明确的fromIntegral。这不太好,但不那么不方便。

作为一般规则,我尝试避免任何比结果更多态的函数参数。 Haskell的多态性最好“向后”,即与动态语言相反的方式:调用者请求结果应该是什么类型,并且编译器从中得出参数应该是什么。这几乎总是有效,因为只要在主程序中以某种方式使用结果,整个计算中的类型就必须链接到树结构。

OTOH,推断结果的类型通常是有问题的:参数可能是可选的,可能只与结果相关联,或者作为多态常量给出,如Haskell数字文字。因此,如果在i的结果中^没有出现,请避免在参数中出现。

<小时/> “避免”并不意味着我不会写它们,除非有充分的理由,否则我不会这样做。