Explicit laziness
tulam evaluates ordinary values strictly. Laziness is an explicit type and operation, so delayed computation cannot be confused with an already available value.
Implementation status: The following forms are normative, but memoized call-by-need is not implemented end to end on the native reference backend.
Lazy values
primitive Lazy(a:Type);
function delay(body:Unit -> a) : Lazy(a) = intrinsic;
function force(item:Lazy(a)) : a = intrinsic;Lazy(a) is distinct from a. There is no implicit automatic force. Evaluation is memoized call-by-need: after successful forcing, later forces share the result.
Expression sugar
~expressiondesugars to:
delay(fn() = expression)~ makes the delay visible at the construction point.
Lazy fields and binders
type Stream(a:Type) =
Empty
+ More * head:a * ~tail:Stream(a);~tail:T means tail:Lazy(T). A lazy function binder similarly binds a Lazy(T) value that must be forced explicitly.
function streamHead[a:Type](~xs:Stream(a)) : Maybe(a) =
match force(xs)
| Empty -> Nothing
| More * head * tail -> Just(head);Effects and suspension
Pure Lazy(a) must not silently capture an effectful computation. Effectful suspension uses an effect-indexed library type so the capability requirements remain in the type.
Why explicit?
Explicit laziness keeps default evaluation and cost predictable, makes sharing observable in the type, and avoids a backend deciding independently which expressions are semantically delayed. Optimization may still avoid unnecessary work when it preserves strict semantics.
Common mistakes
Do not expect implicit forcing, use pure Lazy(a) to hide effects, or rely on the specified memoization behavior on native before its implementation gate is complete.
Recap
Ordinary tulam is strict. Lazy(a), delay, force, and ~ make suspension and sharing an explicit part of a program’s contract.
Normative details: Language Reference §15.