【问题标题】:Help with debugging unexpected takeWhile behaviour with large numbers in Haskell帮助调试 Haskell 中大量的意外 takeWhile 行为
【发布时间】:2010-07-08 22:28:57
【问题描述】:

首先,为模糊的标题道歉,但我不确定究竟我在这里问什么(!)。

在大学遇到 Haskell 后,我最近开始在愤怒中使用它,所以我正在解决 Project Euler 问题作为扩展的 Hello World,真的。我在我的一个答案中遇到了一个错误,这似乎表明对语言的基本部分存在误解,这不是我可以从教程中解决的问题,也不是我对开始谷歌搜索的足够了解。

问题本身的简要描述 - 解决方案与素数有关,所以我想要一个我实现的素数的无限列表(还没有优化!)因此:

isPrime :: Int -> Bool
isPrime n = isPrime' 2 n
            where isPrime' p n | p >= n           = True
                               | n `mod` p == 0  = False
                               | otherwise       = isPrime' (p+1) n

primes :: [Int]
primes = filter isPrime [2..]

由于评估无限列表可能有点乏味,我当然会使用惰性评估来确保只评估我想要的位。因此,例如,我可以向 GHCI 询问小于 100 的素数:

*Main> takeWhile (< 100) primes
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]

现在这是我完全不明白的部分 - 当上限变得足够大时,我根本没有得到任何答案。特别是:

*Main> takeWhile (< 4000000000) primes
[]

这不是takeWhile 本身的问题,也不是部分应用的函数,因为takeWhile (&lt; 4000000000) [2..] 按我的预期工作。我使用过滤器(在primes 的定义内)没有问题,因为takeWhile (&lt; 4000000000) (filter even [2..]) 也返回预期结果。

通过二分搜索,我发现最大的上限是2^31 - 1,所以这似乎意味着某种基于空间的约束(即最大的正符号整数)。然而:

  1. 我的印象是 Haskell 对整数的大小没有语言限制,它们仅受可用内存量的限制。
  2. 这个数字只出现在小于谓词中,我知道它至少在某些情况下可以按预期工作。当然,当它应用于列表的元素时,它不应该关心它们来自哪里?只看第一个元素,我知道当输入 2 来自 filter even [2..] 时,谓词返回 true;我知道 primes 返回 2 作为它的第一个元素。那么我的列表怎么可能是空的,这个谓词怎么会失败“对于某些值 2”?

任何想法都将不胜感激,因为我没有足够的经验知道从哪里开始。感谢您抽出宝贵时间查看。

【问题讨论】:

    标签: debugging haskell integer


    【解决方案1】:

    haskell 中有 2 种内置的整数类型:IntIntegerInteger 是默认值,没有限制。 Int 然而是有界的。由于您在 isPrime 类型中明确使用 Int ,因此 4000000000 被用作 Int 并溢出。如果您将isPrime 的类型更改为Integer -&gt; Bool 甚至更好的Integral a =&gt; a -&gt; Bool(阅读:可以采用任何类型的Integral 值并返回Bool 的函数),它将按预期工作。

    这里要带走的重要一点(除了IntInteger 之间的区别)是 4000000000 的类型取决于它的使用方式。如果将它用作接受Int 的函数的参数,它将是Int(在32 位系统上它会溢出)。如果它用作接受Integer 的函数的参数,它将是Integer(并且永远不会溢出)。如果将它用作采用任何类型Integral 的函数的参数,它也将是Integer,因为IntegerIntegral 的默认实例。

    【讨论】:

    • 谢谢,我现在明白了。所以它实际上确实是 2 的不同值 - IntInteger!然后导致类型推断将字符串4000000000 解释为两种情况下的不同数据类型,后一种是Int,因此负数为几亿。感谢您的全面解释!
    【解决方案2】:

    这是一个简单的答案(...我看到已经部分回答了)-“过早的专业化”。

    定义的第一部分,类型签名,指定:

    isPrime :: Int -> Bool
    

    Int 不仅仅是表达Integer 的“快捷方式” - 它们是不同的类型!要成为一个挑剔者(这反过来又会邀请其他人在这里撕毁我不准确的许多地方),从不2的不同值” - 它有为 Int 类型,因为这是您指定函数的方式(您将 2 与函数的参数 n 进行比较,并且您只能比较相同类型的值,因此您的 2 被“固定”到Int 类型。

    哦,作为警告,Int 类型是一种充满极端情况潜力的类型。如果您的系统是在 64 位环境中构建的,那么您的 Int 也将基于 64 位表示,并且您的示例将工作到 2^63-1,而不是您的 2^31-1做过。注意我的措辞:我有一台带有 MS Windows 操作系统的 64 位计算机,这意味着还没有官方的 64 位 MinGW 工具链——我的操作系统是 64 位的,但我拥有的 GHC 版本是用 32 编译的——位库,因此它具有基于 32 位的Ints。当我使用 Linux 时,即使在 VM 中,它也有一个 64 位的工具链,所以 Ints 是 64 位的。如果您使用过其中一种,您可能甚至都没有注意到这种行为!

    所以,我想这只是在推理您的类型时要小心的另一个原因。 (尤其是在 Haskell 中,无论如何......)

    【讨论】:

    • 非常正确 - 这只是代表我的经验不足,根本没有意识到Integer 是我真正想在那里使用的。从更广泛的意义上讲,Int 类型似乎不是很有用 - 除非您喜欢上溢/下溢的可能性,或者想要真的确定您的确切字节数变量会占用?
    猜你喜欢
    • 1970-01-01
    • 2020-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-10
    • 1970-01-01
    • 1970-01-01
    • 2011-02-24
    相关资源
    最近更新 更多