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 = 42

In 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 map

Even + 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]          ; Bool

Let 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]  ; 15

Note

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 comment

Vectors, 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]]