Haskell中的幂运算

Posted

技术标签:

【中文标题】Haskell中的幂运算【英文标题】:Exponentiation in Haskell 【发布时间】:2011-09-18 00:55:05 【问题描述】:

谁能告诉我为什么 Haskell Prelude 定义了两个独立的求幂函数(即^**)?我认为类型系统应该消除这种重复。

Prelude> 2^2
4
Prelude> 4**0.5
2.0

【问题讨论】:

【参考方案1】:

实际上有三个幂运算符:(^)(^^)(**)^ 是非负整数幂,^^ 是整数幂,** 是浮点幂:

(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a

原因是类型安全:数值运算的结果通常与输入参数具有相同的类型。但是您不能将Int 提高到浮点幂并得到Int 类型的结果。因此类型系统会阻止您这样做:(1::Int) ** 0.5 会产生类型错误。 (1::Int) ^^ (-1) 也是如此。

另一种说法:Num 类型在^ 下关闭(它们不需要有乘法逆),Fractional 类型在^^ 下关闭,Floating 类型在@ 下关闭987654337@。由于Int 没有Fractional 实例,因此不能将其提升为负幂。

理想情况下,^ 的第二个参数将被静态约束为非负数(当前,1 ^ (-2) 会引发运行时异常)。但是Prelude 中没有自然数的类型。

【讨论】:

【参考方案2】:

它没有定义两个运算符——它定义了三个!来自报告:

有三个二元幂运算:(^) 将任何数字提升到非负整数幂,(^^) 将小数提升到任何整数幂,并且 (**) 需要两个浮点数-点论据。对于任何xx^0x^^0 的值为1,包括零; 0**y 未定义。

这意味着存在三种不同的算法,其中两种给出精确结果(^^^),而** 给出近似结果。通过选择要使用的运算符,您可以选择要调用的算法。

【讨论】:

【参考方案3】:

^ 要求它的第二个参数是Integral。如果我没记错的话,如果你知道你正在使用一个积分指数,那么实现会更有效。此外,如果您想要2 ^ (1.234) 之类的东西,即使您的基数是整数 2,您的结果显然也是分数。您有更多选择,因此您可以更严格地控​​制进出取幂函数的类型。

Haskell 的类型系统与其他类型系统(例如 C、Python 或 Lisp)的目标不同。鸭子打字(几乎)与 Haskell 思维方式相反。

【讨论】:

我不完全同意 Haskell 类型的思维方式与鸭式类型相反。 Haskell 类型类很像鸭子类型。 class Duck a where quack :: a -> Quack 定义了我们对鸭子的期望,然后每个实例都指定了可以表现得像鸭子的东西。 @augustss 我知道你来自哪里。但是鸭式打字背后的非正式座右铭是“如果它看起来像鸭子,行为像鸭子,并且像鸭子一样嘎嘎叫,那么它就是鸭子。”在 Haskell 中它不是鸭子,除非它被声明为 Duck 的实例。 没错,但这就是我对 Haskell 的期望。你可以做任何你想要的鸭子,但你必须明确地表达出来。我们不想把我们没有要求的东西误认为是鸭子。 Haskell 的做事方式和鸭式打字之间有一个更具体的区别。是的,你可以给任何类型的 Duck 类,但它不是 Duck。当然,它可以发出嘎嘎声,但具体来说,它仍然是任何类型的。您仍然无法获得 Ducks 列表。接受 Ducks 列表并混合和匹配各种类型的 Duck 类的函数将不起作用。在这方面,Haskell 不允许你只说“如果它像鸭子一样嘎嘎叫,那么它就是鸭子”。在 Haskell 中,你所有的 Ducks 必须是同一类型的 Quackers。这确实与鸭式打字完全不同。 你可以有一个混蛋列表,但是你需要Existential Quantification的扩展。【参考方案4】:

Haskell 的类型系统不足以将三个幂运算符表示为一个。你真正想要的是这样的:

class Exp a b where (^) :: a -> b -> a
instance (Num a,        Integral b) => Exp a b where ... -- current ^
instance (Fractional a, Integral b) => Exp a b where ... -- current ^^
instance (Floating a,   Floating b) => Exp a b where ... -- current **

即使你打开了多参数类型类扩展,这也不起作用,因为实例选择需要比 Haskell 当前允许的更聪明。

【讨论】:

关于这无法实施的说法是否仍然正确? IIRC,haskell 有一个选项,多参数类型类的第二个参数由第一个参数严格确定。除此之外还有其他无法解决的问题吗? @singular 仍然是真的。第一个参数不能确定第二个参数,例如,您希望指数同时为IntInteger。为了能够拥有这三个实例声明,实例解析必须使用回溯,并且没有 Haskell 编译器实现。 “类型系统不够强大” 论点在 2015 年 3 月仍然成立吗? 你当然不能按照我建议的方式编写它,但可能有某种方式对其进行编码。 @ErikAllik 可能适用于标准 Haskell,因为自 2010 年以来没有出现新的 Haskell 报告。

以上是关于Haskell中的幂运算的主要内容,如果未能解决你的问题,请参考以下文章

Haskell 数据类型实例作为运算符

Haskell中的过载角括号作为内积运算符

Haskell $ 运算符是不是有逆运算?

将定点运算符翻译成 Haskell 语言

Scala 是不是有类似于 Haskell 的 `$` 的运算符?

常见的 Haskell 运算符是不是有可发音的名称? [关闭]