Docs / Effects & purity

Effects & purity

Functions are pure by default. To perform side effects — read a file, print, spawn a process — you mark a function foul. Pure code cannot call foul code, enforced at type-check time. The shape of the program tells you where IO happens.

Pure vs foul

purity.kex
let compute(x: Int) = x * 2 + 1     # pure

foul readConfig(path: String) do
  return File.read(path)
end

# Compile error: pure code cannot call foul code.
# let bad(path: String) = readConfig(path)

main is implicitly foul, so programs still have a natural place for IO.

Local mutation

Bindings are immutable by default. var opts into local mutation, and ! rebinds the variable to the method’s updated return value — the underlying value is never mutated in place, so aliases never see the change.

mutation.kex
var list = [1, 2, 3, 4, 5]
list.push!(6)
list.filter!(&.even?)
list.map! { |x| x * 10 }
# list is [20, 40, 60]

# frozen.push!(4)
# runtime error: Cannot use '!' on immutable binding: frozen