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