Tagged blocks
User-defined control structures: define a tag, call it with a trailing block, and let the tag body control execution via yield.
Summary
Tags are defined with tag name(params) { body }. Callers pass a trailing block name(args) { ... }. Inside the tag body, yieldruns the caller's block and returns its value. The tag can yield multiple times (retry loops, timing wrappers) or conditionally. Tagged blocks desugar to regular function calls with the trailing block as a zero-argument lambda. They work at statement level and inside let/var assignments.
Canonical
Tags are user-defined control structures. Define a tag with tag name(params) { body }, then call it with a trailing block: name(args) { block }. Inside the tag body, yield executes the caller's block and returns its result.
-- define a tag that runs a block twice
tag twice() {
yield;
yield;
}
twice() {
println("hello!") -- prints twice
}Tags are useful for wrapping common patterns like retry logic, timing, error suppression, and resource management.
-- retry a block up to n times
import http
tag retry(n) {
var attempts = 0
loop {
try {
let result = yield;
return result
} catch e {
attempts = attempts + 1
if attempts >= n {
throw "failed after {n} attempts: {e}"
}
}
}
}
retry(3) {
let resp = http.get("https://flaky-api.com")
resp["body"]
}-- measure how long a block takes
tag timed() {
import time
let start = time.clock()
let result = yield;
println("took {time.clock() - start}s")
return result
}
timed() {
heavy_computation()
}-- provide a fallback if block returns null
tag with_default(fallback) {
let val = yield;
if val == null { return fallback }
return val
}
let config = with_default(#{}) {
load_config("app.toml")
}Trailing blocks work at both statement level and inside let/var assignments:
let result = suppress() {
might_throw()
}Tags desugar to regular functions with an implicit __block parameter. The trailing block { ... } is passed as a zero-argument lambda. yield inside the tag body calls that lambda.