I am digging into F# source code recently.
in Seq.fs:
// Binding.
//
// We use a type defintion to apply a local dynamic optimization.
// We automatic
When yield!
appears in a non-tail-call position, it essentiall means the same thing as:
for v in do yield v
The problem with this (and the reason why is that quadratic) is that for recursive calls, this creates a chain of iterators with nested for
loops. You need to iterate over the whole sequence generated by
for every single element, so if the iteration is linear, you get a quadratic time (because the linear iteration happens for every element).
Let's say the rwalk
function generates [ 9; 2; 3; 7 ]
. In the first iteration, the recursively generated sequence has 4 elements, so you'd iterate over 4 elements and add 1. In the recursive call, you'd iterate over 3 elements and add 1, etc.. Using a diagram, you can see how that's quadratic:
x
x x
x x x
x x x x
Also, each of the recursive calls creates a new instance of object (IEnumerator
) so there is also some memory cost (although only linear).
In a tail-call position, the F# compiler/librar does an optimization. It "replaces" the current IEnumerable
with the one returned by the recursive call, so it doesn't need to iterate overe it to generate all elements - it is simply returned (and this also removes the memory cost).
Related. The same problem has been discussed in the C# lanaugage design and there is an interesting paper about it (their name for yield!
is yield foreach
).