plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-examples/closures.plum
import std/Str
import std/Bool
import std/Number
fun each(cb: fn(Int) -> Int) -> Int =
cb(5)
fun useCapturingClosure() -> Int =
offset := 100
cb := |v|
v + offset
cb(5)
fun main() -> Int =
each(|v| v * 3) + useCapturingClosure()
fun useSnapshotClosure() -> Int =
x = 10
snapshotCb = |v|
x + v
x = 999
each(snapshotCb)
fun eachF(cb: fn(Float) -> Float) -> Float =
cb(0.0)
fun useFloatClosure() -> Float =
offset = 2.5
floatCb = |v|
offset + v
eachF(floatCb)
enum ClosureCat =
| ClosureCat(age: Int)
fun eachCat(cb: fn(ClosureCat) -> Int) -> Int =
cb(ClosureCat(age: 7))
fun useClassClosure() -> Int =
classCb = |c|
c.age
eachCat(classCb)
fun identity(value: T) -> T =
value
fun eachViaGeneric(cb: fn(Int) -> Int) -> Int =
cb(identity(7))
# `inner` (nested inside `outer`'s body) needs `offset` — a name from
# `useNestedClosure`'s scope, two levels up from `inner` itself, and not
# referenced by `outer` directly — exercising the multi-level capture chain:
# `outer` must itself capture `offset` purely because `inner` needs it.
fun useNestedClosure() -> Int =
offset = 100
outer = |v|
inner = |w|
w + offset
inner(v)
each(outer)
fun doubleForClosureTest(x: Int) -> Int =
x * 2
fun classifySign(n: Int) -> Str =
cb = |v| v > 0 ? "pos" : "nonpos"
cb(n)
test "closures example computes correctly"
assert main() == 120
# ---- closure regression tests ----
test "non capturing closure passed and called runs correctly"
assert each(|v| v) == 5
# Regression test: the external scanner used to never emit a dedent for a
# multi-line closure body immediately followed by `)` on the same line as
# the body's last statement, so this shape didn't parse at all before.
test "multi line closure call argument with closing paren on last statement line runs correctly"
multiLineResult = each(|v|
bumped = v + 1
bumped * 2)
assert multiLineResult == 12
test "capturing closure snapshots value at creation time runs correctly"
# each calls its callback with 5, so a correct snapshot of x==10 (its value
# when the closure was created, not 999 — its value when `each(cb)` is
# actually called) gives 10 + 5 = 15.
assert useSnapshotClosure() == 15
test "closure assigned then called at float type runs correctly"
assert useFloatClosure() == 2.5
test "closure assigned then called at class type runs correctly"
assert useClassClosure() == 7
test "closure passed through already generic higher order function runs correctly"
assert eachViaGeneric(|v| v * 2) == 14
test "nested closure literal runs correctly"
assert useNestedClosure() == 105
test "nested closure literal passed directly as call argument runs correctly"
assert each(|v|
each(|w| w + v)) == 10
test "named function used as a closure typed value runs correctly"
assert each(doubleForClosureTest) == 10
test "named function used as a value assigned then called runs correctly"
f = doubleForClosureTest
assert f(21) == 42
test "named function used as a value alongside a closure at the same call type runs correctly"
# Proves the trampoline shares the same call_indirect type as an ordinary
# closure of the same signature (both must resolve to the same wasm
# function type, since both flow through the exact same `cb(...)` call site).
assert each(doubleForClosureTest) + each(|v| v + 1) == 16
test "closure with an inline ternary body runs correctly"
# A ternary as a closure's own inline body used to parse as an OUTER
# ternary wrapping the whole closure instead (`(|v| v > 0) ? "pos" :
# "nonpos"`), a grammar precedence bug — see the closure/PREC.conditional
# fix in tooling/tree-sitter-plum/grammar.js.
assert classifySign(5) == "pos"
assert classifySign(-3) == "nonpos"