Basics
Expressions, bindings, and fundamental syntax.
Everything is an Expression
Hold onto one idea and the rest of Loon falls into place: there are no statements. Every construct produces a value. if returns a value. let returns a value. A block of expressions returns its last one. Once this clicks, your code changes shape. You stop arranging steps in the order "do this, then do that" and start composing answers in the form "this evaluates to that."
[let x [if true 1 2]] ; x = 1
[let y [do
[println "side effect"]
42]] ; y = 42In the second example, do runs both expressions in sequence, yet the whole block evaluates to 42, because that is the last expression. Side effects happen along the way; the value flows through.
Bracket Syntax
Every function call wears the same uniform: square brackets, the function first, then its arguments. There is no special syntax for operators, method calls, or macros. One shape covers everything: [function arg1 arg2]. The uniformity is the point. You trade a brief unfamiliarity for a language with no precedence rules to memorize and no question about where the parentheses go.
[println "hello"] ; call println
[+ 1 2] ; call +
[map inc #[1 2 3]] ; call mapEven + is a function, with no special standing among the rest.
Primitives
The primitive types are deliberately few: strings, integers, floats, and booleans. Nothing converts between them on its own, so 1 + 1.0 never coerces behind your back. When you want a string, you reach for str; for numbers, you call the conversion you mean.
[let name "Loon"] ; Str
[let age 1] ; Int
[let pi 3.14159] ; Float
[let ok true] ; BoolLet Bindings
Use let to bind a value to a name. Bindings are immutable by default: once you have bound x to 10, that is what it stays. Coming from a mutable-by-default language, this can feel like a cage. It is closer to a guarantee, retiring every bug that begins with the question "who changed this variable?"
[let x 10]
[let y [+ x 5]]
[println y] ; 15Note
There is no def keyword in Loon. Use let for all bindings.
Comments
Line comments start with a semicolon. Put one on its own line or at the end of an expression; everything after the semicolon is yours to say.
; This is a comment
[let x 42] ; inline commentVectors, Maps, and Sets
Three collection literals cover most of what you reach for. Vectors use #[...] for ordered sequences. Maps use {:key value} for key-value associations, with keywords (the :name syntax) as the usual key. Sets use #{...} for unordered, unique collections.
[let nums #[1 2 3]]
[let user {:name "Ada" :age 30}]
[let tags #{"fast" "safe"}]All three are immutable. Operations like append or assoc hand back a new collection and leave the original alone. The Collections guide covers these in detail.
If and Do
Because if is an expression, it always produces a value. It takes a condition, a then-branch, and an optional else-branch, and each branch is a single expression. When a branch needs to do several things, wrap them in [do ...].
[if [> x 0]
"positive"
"non-positive"]
[if ready
[do
[println "starting"]
[run]]]Why no implicit do inside if? Because a branch usually needs one expression, and the explicit do marks the moment you reach for side effects. A small ceremony that keeps the code honest about what it does.
Warning
if branches do NOT have implicit do. Use [do ...] to run multiple expressions in a branch.
Printing
Use println to print a value with a newline, or print without one. To assemble a string from several pieces, str concatenates its arguments into one.
[println "hello world"]
[println [str "x = " x]]