给出以下功能:
f :: [String]
f = map integerToWord [1..999999999]
integerToWord :: Integer -> String
让我们忽略实施。这是一个示例输出:
ghci> integerToWord 123999
"onehundredtwentythreethousandandninehundredninetynine"
当我执行f
时,是否所有结果,即f(0) through f(999999999)
都存储在堆栈或堆中?
注意 - 我假设Haskell有堆栈和堆。
运行此功能约1分钟后,我看不到RAM从其原始用法增加。
答案 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使用以下两种技术:
Haskell中的延迟评估意味着(作为默认规则)表达式仅在需要它们的值时进行评估,即使这样,它们也可能只被部分评估 - 只需要足够的时间来解析需要该值的模式匹配。因此,我们无法在不知道要求其价值的内容的情况下说明您的map
示例。
列表融合是一组内置于GHC中的重写规则,它可以识别出一些好的"输出的情况。列表生成器只被用作" good"的输入。列出消费者。在这些情况下,Haskell可以将生产者和消费者融合到一个对象代码循环中,而无需分配列表单元格。
在你的情况下:
[1..999999999]
是一个优秀的制作人map
既是好消费者又是好生产者-O
编译程序才能进行融合。map
的输出会消耗什么。如果它是一个好的消费者,它将融合map
。但如果您编译(使用-O
)只打印该代码结果的程序,那么GHC很可能会消除大部分或全部列表单元格分配。在这种情况下,列表根本不会作为内存中的数据结构存在 - 编译器会生成与大致相当的内容的对象代码:
for (int i = 1; i <= 999999999; i++) {
print(integerToWord(i));
}