16

I can't wrap my head around the differences between sequence and LazyList. They're both lazy and potentially infinite. While seq<'T> is IEnumerable<'T> from .NET framework, LazyList is included in F# PowerPack. In practice, I encounter sequences much more often than LazyLists.

What are their differences in terms of performance, usage, readability, etc? What are reasons for such a bad reputation of LazyList compared to that of seq?

pad
  • 41,040
  • 7
  • 92
  • 166

2 Answers2

31

LazyList computes each element only once regardless of how many times the list is traversed. In this way, it's closer to a sequence returned from Seq.cache (rather than a typical sequence). But, other than caching, LazyList behaves exactly like a list: it uses a list structure under the hood and supports pattern matching. So you might say: use LazyList instead of seq when you need list semantics and caching (in addition to laziness).

Regarding both being infinite, seq's memory usage is constant while LazyList's is linear.

These docs may be worth a read.

Ringil
  • 6,277
  • 2
  • 23
  • 37
Daniel
  • 47,404
  • 11
  • 101
  • 179
  • +1, cool, I didn't know about caching part. Could you give an example which requires both laziness and list semantics? – pad Feb 08 '12 at 21:19
  • Traversing a sequence while simultaneously accessing multiple elements can be done with a `seq`, but it's much cleaner using `LazyList` + pattern matching. – Daniel Feb 08 '12 at 21:21
  • See e.g. http://stackoverflow.com/questions/3484315/how-to-merge-sorted-sequences-in-f and http://stackoverflow.com/questions/1306140/f-why-is-using-a-sequence-so-much-slower-than-using-a-list-in-this-example/1306267#1306267 – Brian Feb 08 '12 at 21:27
  • @pad: I added a link to the old docs on MSR. It explains them pretty well. – Daniel Feb 08 '12 at 21:41
  • How does caching referred here compare to memoization which in Haskell world means once a value is computed, it is saved and used again if needed in the future? – vis Mar 18 '12 at 23:23
19

In addition to Daniel's answer, I think the main practical difference is how you process the LazyList or seq structures (or computations).

  • If you want to process LazyList, you would typically write a recursive function using pattern matching (quite similar to processing of normal F# lists)

  • If you want to process seq, you can either use built-in functions or you have to write imperative code that calls GetEnumerator and then uses the returned enumerator in a loop (which may be written as a recursive function, but it will mutate the enumerator). You cannot use the usual head/tail style (using Seq.tail and Seq.head), because that is extremely inefficient - because seq does not keep the evaluated elements and the result of Seq.head needs to re-iterate from the start.

Regarding the reputation of seq and LazyList, I think that F# library design takes a pragmatic approach - since seq is actually .NET IEnumerable, it is quite convenient for .NET programming (and it is also nice because you can treat other collections as seq). Lazy lists are not as frequent and so normal F# list and seq are sufficient in most of the scenarios.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553