2

所以,我正在尝试编写自己的 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是一个多态常数,它是 monoidal 操作下的恒等式。)

数字类型是幺半群,所以我可以尝试做,比如 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 都是 Integral 类型,但是为什么在普通 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?

4

1 回答 1

1

Hindley-Milner 类型系统真的不喜欢必须默认任何东西。在这样的系统中,您希望类型被适当地固定(刚性,skolem)或适当地多态,但是“这就像一个整数......但如果你愿意,我也可以将它转换为其他东西”正如许多其他语言所具有的那样并没有真正奏效。

因此,Haskell 不擅长违约。它对此没有一流的支持,只有一个非常hacky的临时硬编码机制,主要处理内置数字类型,但在涉及更多内容时失败了。

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

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

Int可能是有争议的——当然Integer在某种意义上会更安全;但是,指数太大而无法适应Int通常意味着结果无论如何都将完全超出范围,并且无法通过迭代乘法进行计算;所以这种表达的意图很好。它为您只写或类似的极其常见的情况提供了最佳性能x^2,这是您绝对不希望在指数中添加额外签名的情况。

在相当少的情况下,您有一个具体的例如Integer数字并希望在指数中使用它,您总是可以推入一个明确的fromIntegral. 这不是很好,而是不便。

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

OTOH,推断结果的类型通常是有问题的:参数可能是可选的,它们本身可能只链接到结果,或者作为多态常量(如 Haskell 数字文字)给出。因此,如果i的结果中没有^出现,请避免在参数中出现。


†</sup> “避免”并不意味着我从不写它们,我只是不这样做,除非有充分的理由。

于 2017-10-31T17:44:25.460 回答