为什么Haskell的“不执行任何操作”功能id会占用大量内存?


112

Haskell具有一个标识函数,该函数不变地返回输入。定义很简单:

id :: a -> a
id x = x

所以为了娱乐,这应该输出8

f = id id id id id id id id id id id id id id id id id id id id id id id id id id id
main = print $ f 8

几秒钟后(根据任务管理器,大约有2 gb的内存),编译将失败ghc: out of memory。同样,口译员说ghci: out of memory

由于它id是一个非常简单的函数,因此我不希望它在运行时或编译时成为内存负担。所有的内存都用来做什么?


11
您想组成那些id。在VIM中,将光标放在的定义上f,执行以下操作::s/id id/id . id ./g
Tobias Brandt 2014年

Answers:


135

我们知道的类型id

id :: a -> a

当我们专门针对时id id,的副本id具有类型:

id :: (a -> a) -> (a -> a)

然后,当您再次针对的最左侧id进行专门化时id id id,您将获得:

id :: ((a -> a) -> (a -> a)) -> ((a -> a) -> (a -> a))

因此,您看到每个id添加的对象,最左边的类型签名id是其两倍大。

请注意,类型会在编译过程中删除,因此只会占用GHC中的内存。它不会占用程序中的内存。


我记得冈崎在编写嵌入Haskell的RPN计算器时遇到了类似的麻烦。
dfeuer 2014年

3
也许的问题是,GHC是否应该找到一种方法来更优雅地处理这种事情。特别是,当完全写出时,类型非常大,但是有大量重复项—可以使用共享来压缩此类内容吗?有没有有效的方法来处理它们?
dfeuer

5
@dfeuer尝试在ghci中询问类型。您将通过响应速度看到它必须进行适当的共享。我怀疑由于明显的原因,一旦您转换为其他中间表示形式(例如核心),这种共享就会丢失。
丹尼尔·瓦格纳

4
这意味着,如果id重复n多次,则其类型的空间与成正比2^n。在Ryan的代码中推断出的2^27类型除了表示该类型所需的其他结构之外,还需要引用其类型变量,这可能比大多数类型所期望的要大得多。
大卫

58
天真地进行类型推断是双指数的,通过在类型表达式中巧妙地使用共享,您可以将其简化为指数形式。但是不管您做什么,都会有一些相当简单的表达式使类型检查器爆炸。幸运的是,这些在实际编程中不会发生。
2014
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.