turboMaCk / lazy-tree-with-zipper / Lazy

This library lets you delay a computation until later.

Basics


type Lazy a

A wrapper around a value that will be lazily evaluated.

lazy : (() -> a) -> Lazy a

Delay the evaluation of a value until later. For example, maybe we will need to generate a very long list and find its sum, but we do not want to do it unless it is absolutely necessary.

lazySum : Lazy Int
lazySum =
    lazy (() -> sum <| List.range 1 1000000)

Now we only pay for lazySum if we actually need it.

force : Lazy a -> a

Force the evaluation of a lazy value. This means we only pay for the computation when we need it. Here is a rather contrived example.

lazySum : Lazy Int
lazySum =
    lazy (() -> List.sum <| List.range 1 1000000)

sums : ( Int, Int, Int )
sums =
    ( force lazySum, force lazySum, force lazySum )

evaluate : Lazy a -> Lazy a

Evaluate the lazy value if it has not already been evaluated. If it has, do nothing.

lazySum : Lazy Int
lazySum =
    lazy (() -> List.sum <| List.range 1 1000000)

sums : ( Int, Int, Int )
sums =
    let
    evaledSum =
        evaluate lazySum

    in
    ( force evaledSum, force evaledSum, force evaledSum )

This is mainly useful for cases where you may want to store a lazy value as a lazy value and pass it around. For example, in a list. Where possible, it is better to use force and simply store the computed value separately.

Mapping

map : (a -> b) -> Lazy a -> Lazy b

Lazily apply a function to a lazy value.

lazySum : Lazy Int
lazySum =
    map List.sum (lazy (() -> List.range 1 1000000)

The resulting lazy value will create a big list and sum it up when it is finally forced.

map2 : (a -> b -> result) -> Lazy a -> Lazy b -> Lazy result

Lazily apply a function to two lazy values.

lazySum : Lazy Int
lazySum =
    lazy (() -> List.sum <| List.range 1 1000000)

lazySumPair : Lazy ( Int, Int )
lazySumPair =
    map2 (,) lazySum lazySum

map3 : (a -> b -> c -> result) -> Lazy a -> Lazy b -> Lazy c -> Lazy result

map4 : (a -> b -> c -> d -> result) -> Lazy a -> Lazy b -> Lazy c -> Lazy d -> Lazy result

map5 : (a -> b -> c -> d -> e -> result) -> Lazy a -> Lazy b -> Lazy c -> Lazy d -> Lazy e -> Lazy result

Chaining

apply : Lazy (a -> b) -> Lazy a -> Lazy b

Lazily apply a lazy function to a lazy value. This is pretty rare on its own, but it lets you map as high as you want.

map3 : (a -> b -> c -> result) -> Lazy a -> Lazy b -> Lazy c -> Lazy result
map3 f a b c =
    apply (apply (apply (lazy (\() -> f)) a) b) c

It is not the most beautiful, but it is equivalent and will let you create map9 quite easily if you really need it.

andMap : Lazy a -> Lazy (a -> b) -> Lazy b

Piping-friendly version of apply.

map3 : (a -> b -> c -> result) -> Lazy a -> Lazy b -> Lazy c -> Lazy result
map3 f a b c =
    lazy (\() -> f) |> andMap a |> andMap b |> andMap c

andThen : (a -> Lazy b) -> Lazy a -> Lazy b

Lazily chain lazy computations together, for when you have a series of steps that all need to be performed lazily. This can be nice when you need to pattern match on a value, for example, when appending lazy lists:

type List a = Empty | Node a (Lazy (List a))

cons : a -> Lazy (List a) -> Lazy (List a)
cons first rest =
    Lazy.map (Node first) rest

append : Lazy (List a) -> Lazy (List a) -> Lazy (List a)
append lazyList1 lazyList2 =
    let
        appendHelp list1 =
            case list1 of
                Empty ->
                    lazyList2
                Node first rest ->
                    cons first (append rest list2))
  in
  lazyList1 |> Lazy.andThen appendHelp

By using andThen we ensure that neither lazyList1 or lazyList2 are forced before they are needed. So as written, the append function delays the pattern matching until later.