Effects and I/O
esque tracks side effects in the type system. As of v0.13 the
only effect is @io — a function that may write to stdout (or, in
future, to files / the environment). Pure functions (no annotation)
may not call @io functions; @io functions may call anything.
What esque has today
print_i32(x: i32) -> i32 @io— write a decimal integer +\n, returningx.print_f32(x: f32) -> f32 @io— write a decimal float +\n, returningx.print_str(s: string) -> string @io— write a string verbatim, returnings.each(v, f)wherefis any named function whose effect set is a subset of the enclosing function's effect set.
Everything else is pure. There is no file I/O, no environment access, no clock, no randomness, no general stdout writer — but adding any of those is now a stdlib question, not a language one.
The rule
callee.effects ⊆ caller.effects
A pure caller may call only pure callees. An @io caller may call
either pure or @io callees. each(v, f) applies the same rule to
its second argument.
@io fn shout(x: i32) -> i32 = print_i32(x)
@io fn main() -> i32 = {
each(0..3, shout); # ok: shout is @io, main is @io
0
}
fn pure_main() -> i32 = print_i32(1) # type error
The type error names both sides and suggests adding the annotation to the caller.
Why the design
esque is built around the idea that the type system should tell you
what code can do. A function annotated (i32) -> i32 cannot
secretly produce side effects. each does not have to trust an
arbitrary function; it just checks the effect set fits.
What you write today
- Mark
mainas@io fn main()whenever you want to print. - Wrap repeated print logic in your own
@io fn. The pre-v0.13 hardcoded allowlist (print_i32,print_f32) is gone — any effect-correct function works. - Pure-by-default functions stay easy to read: if there is no annotation, there is no I/O.
Beyond @io
Other effects on the long-term roadmap:
@alloc— allocates heap memory (relevant once dynamic-shape tensors arrive).@panic— may abort.@nondet— random / nondeterministic.
These are all ways for the type system to keep promising more
about what a function does. The EffectSet representation is
already a bitmask, so adding each new effect is a one-bit change
plus its propagation rules.