I think this is a good question. Here's a simpler reprogramming:
let test n = [for i in 1 .. n -> Seq.empty] |> List.fold (Seq.map2 max) Seq.empty |> Seq.iter ignore
test creates a sequence of empty sequences, calculates max line by line, and then iterates over the resulting (empty) sequence. You will find that with a high n this will lead to a stack overflow, even though there are no values ββto iterate at all!
Itβs a little difficult to explain why, but here is a hit at him. The problem is that when you add sequences, Seq.map2 returns a new sequence that Seq.map2 its work until it is listed. Thus, when you try to iterate over the resulting sequence, you end up moving deep into the chain of computations of n layers.
As Daniel explains, you can avoid this by evaluating the resulting sequence with impatience (for example, translating it into a list).
EDIT
Here is an attempt to explain what is going wrong. When you call Seq.map2 max s1 s2 , neither s1 nor s2 actually listed; you will get a new sequence, which, when enumerating, will list both of them and compare the obtained values. Thus, if we do something like the following:
let s0 = Seq.empty let s1 = Seq.map2 max Seq.emtpy s0 let s2 = Seq.map2 max Seq.emtpy s1 let s3 = Seq.map2 max Seq.emtpy s2 let s4 = Seq.map2 max Seq.emtpy s3 let s5 = Seq.map2 max Seq.emtpy s4 ...
Then the call to Seq.map2 always returns immediately and uses the constant stack space. However, enumeration s5 requires enumeration s4, which requires enumeration s3, etc. This means that listing s99999 will create a huge call stack that looks something like this:
... (s99996 enumerator).MoveNext() (s99997 enumerator).MoveNext() (s99998 enumerator).MoveNext() (s99999 enumerator).MoveNext()
and we get a stack overflow.