理解Haskell的`map` - 堆栈还是堆?

时间:2015-05-07 02:44:15

标签: haskell

给出以下功能:

f :: [String]
f = map integerToWord [1..999999999]

integerToWord :: Integer -> String

让我们忽略实施。这是一个示例输出:

ghci> integerToWord 123999
"onehundredtwentythreethousandandninehundredninetynine"

当我执行f时,是否所有结果,即f(0) through f(999999999)都存储在堆栈或堆中?

注意 - 我假设Haskell有堆栈和堆。

运行此功能约1分钟后,我看不到RAM从其原始用法增加。

3 个答案:

答案 0 :(得分:6)

准确地说 - 当你"只是执行" f除非您以某种方式使用其结果,否则不会对其进行评估。当你这样做时 - 根据满足呼叫者要求的方式存储它。

在此示例中 - 它没有存储在任何地方:该函数应用于每个数字,结果输出到您的终端并被丢弃。因此,在给定的时刻,您只需分配足够的内存来存储当前值和结果(这是一个近似值,但对于它足够精确的情况)。

参考文献:

答案 1 :(得分:2)

首先:为了分裂头发,以下答案适用于GHC。一个不同的Haskell编译器可以合理地实现不同的东西。

确实存在堆和堆栈。几乎所有东西都在堆上,堆栈上几乎没有任何东西。

例如,考虑表达式

let x = foo 17 in ...

让我们假设优化器不会将其转换为完全不同的东西。对foo的调用根本没有出现在堆栈中;相反,我们在堆上创建一个注释,表示我们需要在某个时刻执行foo 17,并且x成为指向此注释的指针。

所以,回答你的问题:当你打电话给f时,一条说“我们有一天需要执行map integerToWord [1..999999999]”的注释会被存储在堆上,你会得到一个指针。接下来会发生什么取决于你的结果。

例如,如果您尝试打印整个事物,那么是,每次调用f的结果都会在堆上结束。在任何特定时刻,只有一个f的呼叫在堆栈中。

或者,如果您只是尝试访问结果的第8个元素,那么一堆“call f 5某天”注释最终会在堆上加上f 8的结果,再加上一个请注意列表的其余部分。

顺便说一下,那里有一个包(“真空”?),可以打印出你正在执行的实际对象图。你可能会发现它很有趣。

答案 2 :(得分:0)

GHC程序使用堆栈和堆栈......但它并不像您熟悉的热切语言堆栈机器那样起作用。其他人将不得不解释这一点,因为我不能。

回答你的问题的另一个挑战是GHC使用以下两种技术:

  1. 懒惰评估
  2. List fusion
  3. Haskell中的延迟评估意味着(作为默认规则)表达式仅在需要它们的值时进行评估,即使这样,它们也可能只被部分评估 - 只需要足够的时间来解析需要该值的模式匹配。因此,我们无法在不知道要求其价值的内容的情况下说明您的map示例。

    列表融合是一组内置于GHC中的重写规则,它可以识别出一些好的"输出的情况。列表生成器只被用作" good"的输入。列出消费者。在这些情况下,Haskell可以将生产者和消费者融合到一个对象代码循环中,而无需分配列表单元格。

    在你的情况下:

    1. [1..999999999]是一个优秀的制作人
    2. map既是好消费者又是好生产者
    3. 但你似乎在使用ghci,它没有做融合。您需要使用-O编译程序才能进行融合。
    4. 您还没有告诉我们map的输出会消耗什么。如果它是一个好的消费者,它将融合map
    5. 但如果您编译(使用-O)只打印该代码结果的程序,那么GHC很可能会消除大部分或全部列表单元格分配。在这种情况下,列表根本不会作为内存中的数据结构存在 - 编译器会生成与大致相当的内容的对象代码:

      for (int i = 1; i <= 999999999; i++) {
          print(integerToWord(i));
      }