[英]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使用以下两种技术:
Haskell中的惰性求值意味着(作为默认规则)仅在需要它们的值时才对表达式进行求值,即使这样,它们也可能仅被部分求值-仅够解决需要该值的模式匹配所需的程度。 因此,如果不知道要求其值的内容,就无法说出map
示例的功能。
列表融合是GHC中内置的一组重写规则,可识别许多情况,其中“好”列表生产者的输出仅被消耗为“好”列表消费者的输入。 在这些情况下,Haskell可以将生产者和消费者融合到一个对象代码循环中,而无需分配列表单元。
在您的情况下:
[1..999999999]
是一个好的制作人 map
既是良好的消费者又是良好的生产者 -O
编译程序以实现融合。 map
的输出。 如果是一个好的消费者,它将与map
融合。 但是,如果您编译(使用-O
)一个仅打印该代码结果的程序,则GHC有很大的机会消除大部分或所有列表单元分配。 在这种情况下,该列表根本不会作为数据结构存在于内存中-编译器将生成目标代码,其功能大致与此相同:
for (int i = 1; i <= 999999999; i++) {
print(integerToWord(i));
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.