Laziness
Flix uses eager evaluation in most circumstances, but allows the programmer to opt-in to lazy evaluation when appropriate with the lazy
keyword:
let x: Lazy[Int32] = lazy (1 + 2);
The expression won't be evaluated until it's forced:
let y: Int32 = force x;
Note: The
lazy
construct requires the expression it's given to be pure.
Note: Forcing a lazy value that's already been evaluated won't evaluate it for a second time.
Lazy data structures
Laziness can be used to create lazy data structures which are evaluated as they're used. This even allows us to create infinite data structures.
Here for example, is a data structure which implements an infinitely long stream of integers which increase by one each time:
mod IntStream {
enum IntStream { case SCons(Int32, Lazy[IntStream]) }
pub def from(x: Int32): IntStream =
IntStream.SCons(x, lazy from(x + 1))
}
Given this, we can implement functions such as map
and take
:
pub def take(n: Int32, s: IntStream): List[Int32] =
match n {
case 0 => Nil
case _ => match s {
case SCons(h, t) => h :: take(n - 1, force t)
}
}
pub def map(f: Int32 -> Int32, s: IntStream): IntStream =
match s {
case SCons(h, t) => IntStream.SCons(f(h), lazy map(f, force t))
}
So, for example:
IntStream.from(42) |> IntStream.map(x -> x + 10) |> IntStream.take(10)
Will return:
52 :: 53 :: 54 :: 55 :: 56 :: 57 :: 58 :: 59 :: 60 :: 61 :: Nil
Flix provides DelayList
and DelayMap
data structures which already implement this functionality and more:
DelayList.from(42) |> DelayList.map(x -> x + 10) |> DelayList.take(10)