Haskell GHC评估战略

Haskell GHC评估战略,haskell,ghc,lazy-evaluation,Haskell,Ghc,Lazy Evaluation,我对以下代码在使用GHC 7.6.3编译时的执行方式有点困惑 import qualified Data.Map as M main = do let m1 = M.fromList $ zip [1..10000000] [1..] putStrLn $ "Value = " ++ (show $ M.map (+2) m1 M.! 555) 使用ghc--make-O3编译,它得到以下结果: $ /usr/bin/time ./MapLazy Value = 557

我对以下代码在使用GHC 7.6.3编译时的执行方式有点困惑

import qualified Data.Map as M

main = do let m1 = M.fromList $ zip [1..10000000] [1..]
          putStrLn $ "Value = " ++ (show $ M.map (+2) m1 M.! 555)
使用
ghc--make-O3
编译,它得到以下结果:

$ /usr/bin/time ./MapLazy
Value = 557
29.88user 2.16system 0:32.12elapsed 99%CPU (0avgtext+0avgdata 2140348maxresident)k
0inputs+0outputs (0major+535227minor)pagefaults 0swaps
然而,如果我把它改成只显示1000万美元。!555,我的内存使用率要低得多,但占用的时间几乎相同:

$ /usr/bin/time ./MapLazy
555
23.82user 1.17system 0:25.06elapsed 99%CPU (0avgtext+0avgdata 1192100maxresident)k
0inputs+0outputs (0major+298165minor)pagefaults 0swaps

这里到底发生了什么?整个映射是否仅仅因为我读取了一个值就被实例化了?我能以某种方式阻止这一切吗?我的意思是,这是一个二叉搜索树,所以我希望我在新地图上查找的一条路径能够被实际计算。

我想我得到了它。让我们看看
Data.Map.Map
的源代码

map :: (a -> b) -> Map k a -> Map k b
map f m
  = mapWithKey (\_ x -> f x) m

mapWithKey :: (k -> a -> b) -> Map k a -> Map k b
mapWithKey _ Tip = Tip
mapWithKey f (Bin sx kx x l r) 
  = Bin sx kx (f kx x) (mapWithKey f l) (mapWithKey f r)
现在,
mapWithKey
似乎只构建了树的顶级构造函数,并为这两个分支惰性地递归。。。但是:

data Map k a  = Tip 
              | Bin {-# UNPACK #-} !Size !k a !(Map k a) !(Map k a) 

上面我们看到子树是严格的字段!因此,对
mapWithKey
的递归调用将被强制执行,导致整个树将严格更新,而不是延迟更新。

Map
是严格的。。。如果你仔细想一想,你就会明白为什么它很难不出现。为什么它需要严格的脊柱?正如我在问题中所说,我真的希望它只计算从根到叶的一条路径。这在O(logn)中应该是可行的,对吗?@Savui:记住,映射是大小平衡的二叉树。我相信是再平衡迫使脊柱变得狭窄。@JohnL:不,这没有意义,特别是当涉及Data.Map.Map时。该函数不需要重新平衡:它接受一个平衡树并生成另一个具有相同结构的树。@Savui:如果
Data.Map.Map
完全懒惰,它将创建必须引用原始结构的Thunk。只要地图的创建是严格的,完整的地图就会被实例化。这就是我错过的。在发布问题之前,我查阅了mapWithKey的源代码,但没有注意数据地图声明。谢谢后续问题:有没有理由认为如此严格是件好事?GHC邮件列表上是否有一个帖子讨论过这个问题?是的,我想得越多,地图就越像是一个很好的例子,懒惰其实不是坏事。我不明白他们为什么要这么严格。