User Guide

Lazy Values

Use Need.apply to delay a computation until a caller asks for value:

import one.wabbit.data.Need

var runs = 0
val config = Need.apply {
    runs += 1
    "loaded"
}

check(runs == 0)
check(config.value == "loaded")
check(config.value == "loaded")
check(runs == 1)

Use Need.now when a value is already available but should participate in APIs that expect Need.

Mapping and Flat Mapping

map transforms the eventual value. flatMap lets the next step choose another lazy computation:

import one.wabbit.data.Need

val base = Need.now(20)
val result = base
    .map { it + 1 }
    .flatMap { value -> Need.apply { value * 2 } }

check(result.value == 42)

The implementation evaluates deep chains using a manual stack, so large chains of map or flatMap can be forced without recursive stack overflow.

Combining Values

Use zip to combine lazy values:

import one.wabbit.data.Need

val user = Need.now("ada")
val id = Need.apply { 7 }

val label = Need.zip(user, id) { name, number -> "$name-$number" }

check(label.value == "ada-7")

The instance zip returns a Pair, while companion zip overloads accept a combining function.

Recursive Resolvers

Need.build and Need.buildNonNull construct memoized recursive resolver functions:

import one.wabbit.data.Need

val fib = Need.buildNonNull<Int, Int> { self, n ->
    when (n) {
        0, 1 -> Need.now(1)
        else -> Need.zip(self(n - 1), self(n - 2)) { a, b -> a + b }
    }
}

check(fib(6).value == 13)

Use build when the resolver can return null. Null results are returned but not cached, because the nullable cache uses null as its missing-entry marker. Use buildNonNull when every key resolves to a non-null value and should be cached.

Delay

Delay lets higher-level algorithms decide whether values are strict or lazy:

import one.wabbit.data.Delay
import one.wabbit.data.Need

val strict = Delay.strict<Int>()
check(strict.wrap(Need.now(10)) == 10)

val delayed = Delay.need<Int>()
val nested: Need<Need<Int>> = Need.now(Need.apply { 20 })
check(delayed.wrap(nested).value == 20)

Delay.force unwraps the representation, forces one Need layer, and wraps the result again.

Concurrency

Need is not an exactly-once primitive. If several threads force the same unevaluated value concurrently, the underlying function may run more than once. Keep delayed computations pure or idempotent when concurrent forcing is possible.