<div dir="ltr">Hi,<br><br>I found myself wanting a lazy version of Data.Map today, and by &quot;lazy&quot; I mean in the node-subtree pointers. I trust the library writers when they put strictness annotations in the fields of the tree nodes, so I&#39;m wondering what the various implications of lazy subtrees are, beyond the obvious speed hit. Does this cause space leaks beyond what lazy value pointers can already cause? Can someone point me to some reading that discusses this?<br>
<br>Anyway, I&#39;m positing to libraries (rather than haskell-cafe) to gauge opinion about a possible rewrite of Data.Map and IntMap to remove strictness annotations (bangs) from the node constructors and move them into the functions (as seqs).&nbsp; &quot;Rewrite&quot; is maybe too strong of a word. &quot;Significant patch&quot; is more like it. It would involve only those functions that construct Bin values directly, which is not that many. Less than a days work, I think (yes that means I volunteer.)&nbsp; Semantics of everything remains unchanged, but it opens up the possibility for lazy versions of some functions. <br>
<br>The most usefull result of this would be a lazy map (little m). Here&#39;s Data.Map.mapWithKey <br><br>&nbsp; mapWithKey f Tip = Tip <br>&nbsp; mapWithKey f (Bin sx kx x l r) <br>&nbsp;&nbsp;&nbsp; = Bin sx kx (f kx x) (mapWithKey f l) (mapWithKey f r)<br>
<br>De-banged and then restrictified, it would look like this<br><br>&nbsp; mapWithKey f Tip = Tip <br>
&nbsp; mapWithKey f (Bin sx kx x l r) <br>
&nbsp;&nbsp;&nbsp; = seq l&#39; $ seq r&#39; $ Bin sx kx (f kx x) l&#39; r&#39; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; where l = (mapWithKey f l) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r = (mapWithKey f r)<br><br>Looking at the first version, clearly you see that when constructing a new map you should only have to pay for the sub trees that you actually use. If you perform only a handful of lookups then throw the new tree away, why build the whole thing? <br>
<br>To further motivate, let me explain my use case. I have cyclical data structures (graphs, more or less) that I mutate frequently, so I store them in a Map, indexed by some Ord thing, lets say Int, so I&#39;d have something like Map Int [Int] (but not that exactly, and nothing like Data.Graph). This is great for mutations because I can use backtracking, but for lookups it&#39;s a burden on both me and the cpu. So I memoize the thing into something like &quot;data Node a = Node a [Node a]&quot;&nbsp; I can do this memoization using Data.Map.mapWithKey, with the Nodes built therein referring back to the produced Map. But then, what if I only crawl a small portion of this cyclical network of Nodes? Why should I have to pay for the whole thing to be rebuilt? It defeats the purpose of the memoization, which is to amortize the cost of following edges in the mutable graph. <br>
<br>The pro and con as I see it are:<br><br>Pro <br>- More flexible data structure<br><br>Con<br>- Code is more verbose (see Data.Tree.AVL)<br>- Only a few (but important) functions can be made lazy <br><br>To that last point, note that while mapWithKey can be made lazy for both Map and IntMap, only IntMap allows lazy filter and mapMaybe because it doesn&#39;t rebalance. But I&#39;m wondering how much of the tree needs to be forced when rebalancing. Should be only O(log n), right? It also becomes important where the tree is sourced from. The source needs to produce the tree lazily. The regular definition of fromList (= foldr (uncurry insert) empty) admits no laziness, but maybe successive unions could if the sub-maps were nearly disjoint (a not-uncommon case I think.) Does anyone know if any benchmarking has been done to this end?<br>
<br>Finally, I&#39;ll stress once more that the semantics of the functions currently exported would be unchanged. This would only allow new lazy versions, named something like mapWithKeyL or unionL.<br><br>So what do you think? Too much for too little?<br>
<br>Scott<br><br></div>