Sequence Expressions and Laziness
Prerequisites: Arrays and sequences
You've seen seq { ... } briefly. It's a sequence expression:
code that produces values on demand with yield.
yield and yield!
let countdown n =
seq {
for i in n .. -1 .. 1 do
yield i
yield 0
}
let withHeader items =
seq {
yield "--- start ---"
yield! items
yield "--- end ---"
}
printfn "%A" (countdown 3 |> Seq.toList)
withHeader ["a"; "b"] |> Seq.iter (printfn "%s")
[3; 2; 1; 0]
--- start ---
a
b
--- end ---
yield! ("yield bang") yields every item of another sequence.
Infinite and recursive sequences
let rec fibsFrom a b =
seq {
yield a
yield! fibsFrom b (a + b)
}
let fibonacci = fibsFrom 0L 1L
printfn "%A" (fibonacci |> Seq.take 10 |> Seq.toList)
printfn "%d" (fibonacci |> Seq.find (fun n -> n > 1000L))
let primes =
Seq.initInfinite id
|> Seq.filter (fun n -> n > 1 && Seq.forall (fun d -> n % d <> 0) (seq { 2 .. int (sqrt (float n)) }))
printfn "%A" (primes |> Seq.take 10 |> Seq.toList)
[0L; 1L; 1L; 2L; 3L; 5L; 8L; 13L; 21L; 34L]
1597
[2; 3; 5; 7; 11; 13; 17; 19; 23; 29]
Only the items you ask for (Seq.take 10) are ever computed.
Laziness has a catch
A sequence is a recipe, not a stored result — every time you iterate it, it runs again:
let numbers =
seq {
for i in 1 .. 3 do
printfn " computing %d" i
yield i
}
printfn "sum = %d" (Seq.sum numbers)
printfn "max = %d" (Seq.max numbers)
let cached = numbers |> Seq.cache
printfn "cached sum = %d" (Seq.sum cached)
printfn "cached max = %d" (Seq.max cached)
computing 1
computing 2
computing 3
sum = 6
computing 1
computing 2
computing 3
max = 3
computing 1
computing 2
computing 3
cached sum = 6
cached max = 3
Use Seq.cache, or convert to a list or array (Seq.toList), when a
sequence is expensive and used more than once.
lazy values
lazy delays a single computation until .Force() (or .Value) is
called, and remembers the result:
let expensive =
lazy (
printfn "calculating..."
42
)
printfn "before"
printfn "%d" (expensive.Force())
printfn "%d" (expensive.Force())
before
calculating...
42
42
Streaming data
Laziness is what lets you process a huge file one line at a time:
File.ReadLines path |> Seq.filter ... |> Seq.length never holds the
whole file in memory. You'll use this in the files lesson.