繁体   English   中英

了解Haskell的`map`-堆还是堆?

[英]Understanding Haskell's `map` - Stack or Heap?

赋予以下功能:

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

integerToWord :: Integer -> String

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

ghci> integerToWord 123999
"onehundredtwentythreethousandandninehundredninetynine"

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

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

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

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

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

参考文献:

第一:要分开头发,以下答案适用于GHC。 不同的Haskell编译器可能会以不同的方式实现事物。

确实有一个堆和一个堆栈。 几乎所有东西都放在堆上,几乎没有东西放在堆栈上。

例如,考虑以下表达式

let x = foo 17 in ...

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

因此,回答您的问题:调用f ,将在堆上存储一条注释,指出“我们需要在某天执行map integerToWord [1..999999999] ”,并获得指向该指针的指针。 接下来会发生什么取决于你与结果什么。

例如,如果尝试打印整个内容,则可以,每次调用f的结果都将出现在堆上。 在任何给定的时刻,堆栈上都只有一个对f的调用。

另外,如果您只是尝试访问结果的第8个元素,则堆中会出现一堆“有朝一日打电话给f 5 ”的音符,加上f 8的结果,再加上其余列表的音符。

顺便说一句,那里有一个程序包(“ vacuum”?),它可以让您打印出要执行的实际对象图。 您可能会发现它很有趣。

GHC程序使用堆栈和堆...但是它根本不像您熟悉的急切语言堆栈计算机那样工作。 其他人将不得不解释这一点,因为我不能。

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

  1. 懒惰评估
  2. 清单融合

Haskell中的惰性求值意味着(作为默认规则)仅在需要它们的值时才对表达式进行求值,即使这样,它们也可能仅被部分求值-仅够解决需要该值的模式匹配所需的程度。 因此,如果不知道要求其值的内容,就无法说出map示例的功能。

列表融合是GHC中内置的一组重写规则,可识别许多情况,其中“好”列表生产者的输出仅被消耗为“好”列表消费者的输入。 在这些情况下,Haskell可以将生产者和消费者融合到一个对象代码循环中,而无需分配列表单元。

在您的情况下:

  1. [1..999999999]是一个好的制作人
  2. map既是良好的消费者又是良好的生产者
  3. 但是您似乎正在使用ghci,它不会进行融合。 您需要使用-O编译程序以实现融合。
  4. 您还没有告诉我们什么会消耗map的输出。 如果是一个好的消费者,它将与map融合。

但是,如果您编译(使用-O )一个仅打印该代码结果的程序,则GHC有很大的机会消除大部分或所有列表单元分配。 在这种情况下,该列表根本不会作为数据结构存在于内存中-编译器将生成目标代码,其功能大致与此相同:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM