Basics
Expressions, bindings, and fundamental syntax.
Everything is an Expression
This is probably the single most important thing to internalize about Loon: there are no statements. Every construct produces a value. if returns a value. let returns a value. A block of expressions returns the last one. Once this clicks, you start writing code differently. You stop thinking in terms of "do this, then do that" and start thinking in terms of "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 but the whole block evaluates to 42, because that's the last expression. Side effects happen along the way, but the value flows through.
Bracket Syntax
Loon uses square brackets for all function calls. The first element is the function, and everything after it is an argument. That's it. There's no special syntax for operators, method calls, or macros. Everything follows the same shape: [function arg1 arg2]. This consistency is a deliberate tradeoff. It takes a few minutes to get used to, but it means you never have to wonder about precedence rules or where the parentheses go.
[println "hello"] ; call println
[+ 1 2] ; call +
[map inc #[1 2 3]] ; call mapEven + is just a function. There's nothing magical about it.
Primitives
Loon keeps its primitive types simple. You get strings, integers, floats, and booleans. No implicit conversions between them, so 1 + 1.0 won't silently coerce. You'll use str to convert things to strings when you need to, and explicit conversion functions for numbers.
[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, which means once you've bound x to 10, that's what it is. This sounds restrictive if you're coming from a mutable-by-default language, but in practice it eliminates a whole class of bugs where you're wondering "who changed this variable?"
[let x 10]
[let y [+ x 5]]
[println y] ; 15There is no def keyword in Loon. Use let for all bindings.
Comments
Line comments start with a semicolon. You can place them on their own line or at the end of an expression. Nothing fancy here.
; This is a comment
[let x 42] ; inline commentVectors, Maps, and Sets
Loon gives you three collection literals. Vectors use #[...] and are your go-to ordered sequence. Maps use {:key value} for key-value associations, with keywords (the :name syntax) as the typical key type. 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 return new collections rather than mutating the original. The Collections guide covers these in detail.
If and Do
Since if is an expression, it always produces a value. It takes a condition, a then-branch, and an optional else-branch. Each branch is a single expression. If you need to do multiple things in a branch, wrap them in [do ...].
[if [> x 0]
"positive"
"non-positive"]
[if ready
[do
[println "starting"]
[run]]]Why doesn't if have an implicit do in its branches? Because most of the time you only need one expression, and the explicit do makes it obvious when you're introducing side effects. It's a small bit of ceremony that keeps your code honest about what it's doing.
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. When you need to build up a string from multiple pieces, str concatenates its arguments into a single string.
[println "hello world"]
[println [str "x = " x]]