tur/forms/control
binding, branching, looping, early exit and cleanup.
The forms every function body is made of. They are special forms, so they are always available and take their arguments unevaluated where the syntax says so (quote, the arms of case and match). Definitions (defn, def, defstruct, ...) are on tur/forms/definitions; effects and continuations have pages of their own.
Where the compiled program and the interpreter (tur --interpret, the REPL, the playground) differ, an entry says so. Guides: binding-forms-guide, sum-types-guide (for match), macros-guide (gensym), dynamic-vars-guide (binding), error-handling-guide (defer). What is still to change: docs/upcoming/quote-list-data-plan.md (quote of a list, behind --enable=quote-data).
let
(let [name value ...] body ...) (let name [name value ...] body ...)
bind local names, then evaluate a body.
The binding vector holds name value pairs, and each value sees the names bound before it, so (let [x 1 y (+ x 1)] ...) works. A binding may carry a type, x : int 3 (or fused, x :int 3), and a leading ^mut makes the name assignable with set!. A name bound twice shadows the earlier one. The body is one or more forms and the value is the last. (let name [i 0] ...) is a named let: name is a function of the bound names, so the body can loop by calling it. Vector destructuring, (let [[a b] x] ...), takes a session pair apart (make-session, recv, make-protocol); on a vector or list it is a compile error, since it does not bind the elements -- read them with vec-get. A ref, rc or owning field bound here is released when the body ends.
| bindings | a vector of name value pairs (with optional ^mut and type) | |
| body | the forms evaluated with the names in scope |
The value of the last body form.
(let [x 1 y (+ x 1)] (* x y)) ; => 2 (let loop [i 5 acc 0] (if (= i 0) acc (loop (- i 1) (+ acc i)))) ; => 15
let*
(let* [name value ...] body ...)
deprecated: a second spelling of let.
let already lets each value see the names before it, so let* -- which desugars to nested lets, one per binding -- behaves the same. It is deprecated (TUR-D0006, a warning; an error under --Werror=deprecated): write let. Under #lang r7rs it is the Scheme report's let* and is not deprecated.
| bindings | a vector of name value pairs | |
| body | the forms evaluated with the names in scope |
The value of the last body form.
(let* [a 1 b (+ a 1)] (+ a b)) ; => 3
letrec
(letrec [name value ...] body ...)
bind names that can refer to each other.
For local functions that call each other: each fn can call any of the others, including ones bound later in the same vector. A plain let cannot, because a name is visible only after its binding.
| bindings | a vector of name (fn ...) pairs | |
| body | the forms evaluated with the names in scope |
The value of the last body form.
(letrec [ev? (fn [n : int] : bool (if (= n 0) true (od? (- n 1)))) od? (fn [n : int] : bool (if (= n 0) false (ev? (- n 1))))] (ev? 10)) ; => true
if
(if cond then else) (if cond then)
choose between two expressions.
The condition must be a bool: (if 1 2 3) is if condition must be bool, got int. Both branches must have the same type (if branches have mismatched types). With no else branch the form has type nil, so it can be used for effect but not as a value to print or add to. Only the branch taken is evaluated.
| cond | a bool expression | |
| then | evaluated when cond is true | |
| else | evaluated otherwise (optional) |
The value of the branch taken.
(if (< 1 2) 10 20) ; => 10
do
(do form ...)
evaluate forms in order.
Runs each form and returns the last one's value. (do) with nothing in it is nil. Use it where one form is expected but several steps are needed, such as an if branch or a handle clause body. A defer directly inside a do belongs to that do's scope and fires when the do ends.
| forms | the forms to run |
The value of the last form.
(do 1 2 3) ; => 3
while
(while cond body ...)
repeat a body while a condition holds.
The condition is re-evaluated before every pass and must be a bool. The body may have several forms. The loop has type nil, so it is a statement: keep its result in a ^mut variable the body updates with set!. A defer in the body fires at the end of each pass.
| cond | a bool expression tested before each pass | |
| body | the forms to repeat |
(let [^mut n 0] (while (< n 3) (set! n (+ n 1))) n) ; => 3
set!
(set! name value) (set! (.field place) value)
assign to a mutable place.
Targets: a local bound ^mut; a global declared (def ^mut name ...); a struct field, (set! (.x p) v), where p is a ^mut struct binding or an rc to the struct; and a dynamic variable, inside a binding frame (TUR-E0601 with none, TUR-E0605 inside atomically). Assigning to a name not bound ^mut is 'x' is immutable; use ^mut at the binding site, and the value must have the binding's type (value type float does not match binding type int). The form has type nil.
| target | the name, or (.field place), to assign | |
| value | the new value |
(let [^mut x 1] (set! x 5) x) ; => 5
return
(return value)
leave the enclosing function early with a value.
Valid only inside a function or fn body (at module top level it is return is not allowed at module top level). It returns from the nearest function or lambda, not from the nearest loop or let. The value is computed first; then the deferred cleanups registered by the code it leaves run, and a ref or rc owned by a let it jumps out of is released (unless the value is that binding). The value is checked against the declared return type as the body's last form is: (defn f [] : int (return "a") 1) is TUR-E0709 (an int literal returned from a float function is widened, as there).
| value | the value to return |
(defn first-neg [a : int b : int] : int (when (< a 0) (return a)) (if (< b 0) b 0)) (first-neg -2 5) ; prints -2
defer
(defer body ...)
run a body when the enclosing scope exits.
Registers cleanup to run later, last registered first. The scope is the innermost block it appears in -- a function or let body, a do, a when, or one pass of a while body -- as with Zig's and Swift's defer (Go's is function-wide). It runs when that block ends: on normal exit, on return (innermost block first), and while a panic unwinds. The cleanup reads its variables when it runs, not when defer registers it: after (let [^mut x 10] (defer (println x)) (set! x 20)) it prints 20. That holds for a ^mut number, bool, cstr, Sym or payload-free opaque; a ^mut struct or other aggregate is still copied when defer runs. A defer that panics while one is already unwinding aborts the program.
| body | the forms to run at scope exit |
(defn f [] : int (defer (println "first registered")) (defer (println "second registered")) (println "body") 0) ; calling (f) prints: body, second registered, first registered (do (defer (println "d")) (println "body")) (println "after") ; prints body, d, after
quote
(quote x)
produce a symbol, or a quoted form as data, without evaluating it.
(quote hello), or 'hello, is the Sym for the name, usable with sym->str and =. It is how a macro or DSL helper builds names. A quoted literal is the literal itself.
Under --enable=quote-data a quoted list or vector is a Datum (tur/datum): '(+ 1 2) is (DList [(DSym +) (DInt 1) (DInt 2)]), println prints it as (+ 1 2), and match takes it apart -- on every engine, and in Saffron through an unannotated parameter too. tur/datum loads on first use: when any file the program reads (the entry, a loaded file, an imported module) quotes a list, or defines a macro that expands to one, with no load to write. In a defmacro* body a quoted list is instead the Syntax it spells, as the quasiquote with nothing unquoted is, so it can be returned as the expansion. Without the flag a quoted list is still elaborated as code: (quote (+ 1 2)) gives 3. The flag is in beta and graduates in 0.71.0; see docs/upcoming/quote-list-data-plan.md.
| x | a symbol, literal, list or vector, not evaluated |
The symbol (a Sym), the literal, or (with quote-data) the Datum --
the Syntax in a defmacro* body.
(sym->str (quote hello)) ; => "hello" (datum-length '(+ 1 2)) ; => 3, with --enable=quote-data
case
(case value test result ... :else default)
pick a result by comparing a value with constants.
Each test result pair is tried in order and the first test equal to the value wins; tests are evaluated expressions ((* k 2) works) and compared with =, so they must be numbers, booleans or Syms, not strings. A final :else (or bare else) result is the default. With no default and no match the form is nil, so when the results are values the missing default is if branches have mismatched types; results that are statements need no default. For patterns that bind variables, use match.
| value | the expression to compare | |
| arms | test result pairs, then an optional :else result |
The result of the first matching arm, or of :else.
(case :b :a 1 :b 2 :else 0) ; => 2 (case 7 1 10 2 20 :else 0) ; => 0
match
(match value pattern result ...)
take a value apart by pattern.
Arms are flat pattern result pairs, tried in order. A pattern is a constructor with variables, (Circle r) or (Some v) / (None); an integer literal; a variable that binds the value; or _, which matches anything. A variable may carry a guard, x when (< x 0). Every arm must have the same type. On an algebraic data type the compiler checks exhaustiveness: non-exhaustive patterns -- constructor 'Rect' of 'Shape' not covered is an error (^non-exhaustive opts out). A match on literals has no constructor list to run out of, so it must end with an unguarded _ or variable arm (a bool match may name both true and false instead); without one the compiler reports non-exhaustive patterns -- a match on literal values needs a wildcard arm.
| value | the expression to match | |
| arms | pattern result pairs |
The result of the first matching arm.
(match 5 0 "zero" _ "many") ; => "many" (match -4 x when (< x 0) "neg" _ "pos") ; => "neg"
->
(-> x step ...)
thread a value through calls as the first argument.
(-> x (f a) g) is (g (f x a)): each step receives the previous value as its first argument, and a bare name is called with it alone. (-> x) is x.
| x | the value to thread | |
| steps | calls, or names of one-argument functions |
The result of the last step.
(-> 5 (+ 1) (* 2)) ; => 12
->>
(->> x step ...)
thread a value through calls as the last argument.
Like ->, but each step receives the previous value as its LAST argument: (->> 5 (- 100)) is (- 100 5).
| x | the value to thread | |
| steps | calls, or names of one-argument functions |
The result of the last step.
(->> 5 (- 100)) ; => 95
^tailcall
(^tailcall call)
assert that a call is a tail call.
Marks the call that follows as one that must run in constant stack, in tail position of its function; if it is not, compilation fails with TUR-E0716 (tur explain TUR-E0716) instead of overflowing the stack later. Write it around the call, (^tailcall (f x)), or as a prefix, ^tailcall (f x). A call in an argument position, like (+ 1 (^tailcall (f x))), is the error. The interpreter checks the same tail grammar and gives the same message; the reasons that need the emitter's analyses (an owned local still live, a fn parameter passed on changed) are reported when compiled only.
| call | the call that must be in tail position |
(defn count-down [n : int acc : int] : int (if (= n 0) acc (^tailcall (count-down (- n 1) (+ acc 1))))) (count-down 1000000 0) ; prints 1000000, with no stack growth
gensym
(gensym) (gensym prefix)
make a fresh symbol for a macro to bind.
Used inside a defmacro template, where it runs at expansion time: each call returns a name that cannot collide with a user's, so the macro's own binding cannot capture a variable of the same name at the call site. It takes an optional string or symbol as a prefix. Outside a macro it has no value ((gensym) has no runtime value).
| prefix | an optional string or symbol to start the name with |
A fresh symbol, unique in the program.
(defmacro safe [e] (let [t (gensym "tmp")] `(let [~t 99] (+ ~t ~e)))) (let [tmp 1] (safe tmp)) ; prints 100; a hand-written `tmp` would give 198
binding
(binding [*var* value ...] body ...)
rebind dynamic variables for the extent of a body.
The variables are those declared with defdynamic. Inside the body, and in every function it calls, the variable reads as the new value; when the body ends the old value returns. Bindings nest, and they are per thread. set! of a dynamic variable is allowed only while a binding frame is active.
| bindings | a vector of *var* value pairs | |
| body | the forms evaluated with the new values |
The value of the last body form.
(defdynamic *depth* :int 0) (defn show [] : int *depth*) (binding [*depth* 5] (show)) ; prints 5; outside, (show) is 0
fn
(fn [name : type ...] : type body ...)
an anonymous function that can capture variables.
(fn [x : int] : int (* x 2)): a parameter vector with types, an optional return type, then the body. It captures the variables it uses from the enclosing scope, and it is a value: bind it, pass it, return it. return inside it leaves the lambda, not the function around it. The Greek letter lambda (U+03BB) is accepted as a spelling of fn. Write a lambda with its own parentheses even in sweet-expression files, so its end stays visible.
| params | a vector of name : type | |
| body | the forms evaluated when the function is called |
A function value.
((fn [x : int] : int (* x 2)) 4) ; => 8 (let [k 10 f (fn [x : int] : int (+ x k))] (f 1)) ; => 11