如果我在GHCI中输入[1/0..1/0],我会得到无限的无穷大。为什么?

19

我不能理解在Haskell中range的以下行为。枚举1到1会给我一个只包含1的列表,而2到2会给我一个只包含2的列表。

Prelude> [1..1]
[1]
Prelude> [2..2]
[2]

但是,将无限枚举到无限会给我一个长度无限且所有元素都是无限的列表,如下所示。

Prelude> [1/0..1/0]
[Infinity,Infinity,Infinity,Infinity,Infinity,Infinity,Interrupted.

我知道无穷大是一个概念,不能被看作数字,但是是什么原因导致了这种行为呢?


8
这实际上比 IEEE754 非实数值的许多其他后果更为合理。 - leftaroundabout
3个回答

31
Haskell的Double(在使用/时默认使用)遵循IEEE 754标准,该标准定义了Infinity的行为方式。这就是为什么1/0等于Infinity

根据这个标准(公平地说,也是根据逻辑),Infinity + 1 == Infinity,而DoubleEnum实例每次只加1

这是DoubleEnum 实例不完全合理且不符合我们通常对 Enum 实例的期望的另一个迹象。因此,一般来说,应该避免使用..表示浮点数:即使理解它的工作原理,其他阅读您代码的人也会感到困惑。作为另一个混淆行为的例子,请考虑:

Prelude> [0.1..1]
[0.1,1.1]

如果您想更详细地了解 枚举 实例的 Double,可以阅读这个相当冗长的 Haskell-cafe 线程


很抱歉误点了踩一下,一旦不再被锁定,我会立即将其移除。 - Slade
@Slade:啊,没问题。我添加了一些进一步阅读的链接,所以你的投票不应该再被锁定了。 - Tikhon Jelvis

13

正如Luis Wasserman和Tikhon Jelvis所指出的,基本问题在于FloatDoubleNumEnum实例非常奇怪,并且根本不应存在。实际上,Enum类本身就很奇怪,因为它试图同时服务于几个不同的目的,但都做得不好——它可能最好被认为是一个历史意外和方便而不是类型类的良好示例。与NumIntegral类相比,情况在很大程度上也是如此。在使用任何这些类时,您必须密切关注您正在操作的具体类型。

浮点数的enumFromTo方法基于以下函数:

numericEnumFromTo :: (Ord a, Fractional a) => a -> a -> [a]
numericEnumFromTo n m = takeWhile (<= m + 1/2) (numericEnumFrom n)

所以2 <= 1+1/2false,但由于浮点数的奇怪性质,infinity <= infinity + 1/2true

如Tikhon Jelvis所指出的那样,通常最好不要在浮点数中使用任何Enum方法,包括数字范围。


这正是为什么Frege中没有Enum DoubleEnum Float的原因。 - Ingo

3
我认为实现 [a..b] 的方式是将 a 逐一增加,直到大于 b。由于无限大永远不会达到这个条件,所以它会一直进行下去。
我认为你的代码可能默认使用 Double,这种类型对于无限大有明确定义的语义。据我所知,Haskell 遵循 http://en.wikipedia.org/wiki/IEEE_floating_point

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接