Skip to content

Effects

An effect is a lexically scoped value: declared once, used like any other value, and bound for a region of code with withFx:. Effects explains the design.

defx: name(P1, P2) R
Declares an effect that’s a function, in the same shape as a function declaration.
defx: name Type
Declares an effect of any type — a function, a record, a host object.
defx: name(…) R default, defx: name Type default
As above, with a default value, used wherever the effect isn’t bound.
defx: now() Int
defx: log(Str) Str println
defx: config {.port, .host}

defx: is only allowed at the top level. An effect from another namespace is referred to through a require alias, m/log.

An effect is used like any other value: log("starting"), .port(config).

A top-level def: whose value — as opposed to a function body — uses an effect is an error: effects can only be used within a function body.

withFx: [effect1 value1, effect2 value2, …] body…
Evaluates the body with each effect bound to its value. Each value is checked against the effect’s declared type, with its type variables held rigid: a value narrower than the declaration is an error — the handler for get is less general than its declared type. A defx: default is checked the same way. The binding applies to everything evaluated within the body, however deeply nested the calls, including tasks spawned there. Code outside the body is unaffected. An effect is named in withFx: as it would be used: log, or m/log from another namespace.
def: fixedClock()
1000
def: quietly(msg)
msg
withFx: [now fixedClock, log quietly]
completeTodo(milk)

The compiler infers the set of effects every expression uses: the effects it refers to directly, and those of the functions it calls. Effects aren’t annotated.

Binding an effect removes it from the set, and adds the effects its bound value uses:

defx: log(Str) Str
defx: stdio(Str) Str println
def: logViaStdio(msg)
stdio(msg)
def: doWork() // uses log
log("starting")
def: main(args) // uses stdio
withFx: [log logViaStdio]
doWork()