This guide introduces Turmeric through short, self-contained examples. It
covers the same ground as the interactive REPL tutorial
(docs/guides/repl-tutorial.md) but in prose form, with code blocks you can
paste or run from a file.
By the end you will have seen: expressions, functions, conditionals, recursion,
Option, Result, vectors, closures, structs, and algebraic effects.
CLI: run tur repl in your terminal. Exit with :quit or Ctrl-D.
Web REPL: open the Try Turmeric page in your browser -- no installation needed.
Turmeric is a Lisp-family language. Every operation is written as a parenthesised list with the operator first:
(+ 1 2) ; => 3
(* 6 7) ; => 42
(- 100 58) ; => 42
Strings are double-quoted. Booleans are true and false. println prints
a value followed by a newline:
(println "Hello, Turmeric!") ; prints: Hello, Turmeric!
(= 1 1) ; => true
(not true) ; => false
letlet introduces names scoped to its body:
(let [x 10 y 20] (+ x y)) ; => 30
All bindings in the same let share the same scope; they cannot refer to
each other. Nest let forms when you need sequential bindings.
defn defines a named function. Parameter types and return type are
annotated with :type:
(defn square [x :int] :int (* x x))
(square 9) ; => 81
In the REPL, top-level definitions persist across expressions. :doc square
prints the signature; :type (square 3) shows the inferred type without
evaluating.
if as an expressionif always produces a value -- both branches are required:
(defn abs [n :int] :int
(if (< n 0) (- 0 n) n))
(abs -5) ; => 5
(abs 5) ; => 5
condcond chains tests in order. :else is the fallback:
(defn sign [n :int] :int
(cond (> n 0) 1
(< n 0) -1
:else 0))
(sign 3) ; => 1
(sign -3) ; => -1
(sign 0) ; => 0
when and unlessUse when and unless for side effects that only run under a condition:
(when (< x 0) (println "negative"))
(unless (= x 0) (println "non-zero"))
Both return nil when the condition is not met.
Turmeric has no loop keyword. Repeat work through recursion or the for
macro (see Collections below):
(defn factorial [n :int] :int
(if (<= n 1) 1 (* n (factorial (- n 1)))))
(factorial 10) ; => 3628800
Option: values that might be absentoption-some wraps a value; option-none represents absence.
(option-some? (option-some 42)) ; => true
(option-none? (option-none)) ; => true
(option-some? (option-none)) ; => false
Extract the value with option-unwrap (panics on none) or provide a fallback
with option-unwrap-or:
(option-unwrap (option-some 99)) ; => 99
(option-unwrap-or (option-none) -1) ; => -1
Returning Option instead of crashing is idiomatic for operations that
might not produce a value:
(defn safe-div [a :int b :int]
(if (= b 0) (option-none) (option-some (/ a b))))
(option-unwrap (safe-div 10 2)) ; => 5
(option-unwrap-or (safe-div 10 0) -1) ; => -1
Result: success or failureok wraps a success value; err wraps an error value:
(ok? (ok 100)) ; => true
(err? (ok 100)) ; => false
(result-unwrap-or (err 0) -1) ; => -1
Use Result when the error case carries a useful value (an error code, a
message, a structured error type) that the caller should inspect.
condCombine predicates inside cond to dispatch on either type:
(defn describe-result [r]
(cond (ok? r) (println "ok!")
(err? r) (println "err!")
:else (println "unknown")))
(defn describe-option [o]
(cond (option-some? o) (println "some!")
(option-none? o) (println "none!")
:else (println "unknown")))
See docs/guides/error-handling-guide.md for the full story: panic,
result-must, contract macros, and more.
vec-new allocates an empty growable array. vec-push! appends in place.
vec-get reads by zero-based index. vec-len returns the current length:
(let [v (vec-new)]
(vec-push! v 10)
(vec-push! v 20)
(vec-push! v 30)
(println (vec-len v)) ; 3
(println (vec-get v 1))) ; 20
The ! suffix on vec-push! signals mutation -- the function modifies the
vector in place rather than returning a new one.
for macro(for i lo hi body) binds i to each integer in [lo, hi) and evaluates
body once per value:
(for i 0 5 (println i))
; prints 0 through 4
Combining for with a vector is a common pattern for building sequences:
(let [squares (vec-new)]
(for i 1 6
(vec-push! squares (* i i)))
(for i 0 5
(println (vec-get squares i))))
; prints 1 4 9 16 25
fnfn creates a function value that captures its lexical scope:
(let [add5 (fn [x :int] :int (+ x 5))]
(add5 10)) ; => 15
Closures can be returned from functions, giving each call site its own captured environment:
(defn make-adder [n :int] (fn [x :int] :int (+ x n)))
(let [add3 (make-adder 3)
add7 (make-adder 7)]
(println (add3 10)) ; 13
(println (add7 10))) ; 17
Functions are first-class values. Pass them to other functions:
(defn apply-twice [f x :int] :int (f (f x)))
(apply-twice (fn [x :int] :int (* x 2)) 3) ; => 12
apply-twice is not special -- it is just a function whose first parameter
happens to be another function.
defstruct defines a named record type. The constructor shares the struct
name; field accessors are generated as StructName-fieldname:
(defstruct Point [x :int y :int])
(let [p (Point 3 4)]
(println (Point-x p)) ; 3
(println (Point-y p))) ; 4
Struct values are heap-allocated. :type (Point 1 2) in the REPL confirms
the type.
A complete example combining struct and function:
(defstruct Rect [width :int height :int])
(defn area [r] :int
(* (Rect-width r) (Rect-height r)))
(area (Rect 6 7)) ; => 42
Effects separate the declaration of an operation from its implementation. Instead of calling a concrete function, a computation performs an effect; a handler installed by the caller decides what to do.
defeffect declares a named effect:
(defeffect Log [msg :cstr] :void)
perform raises the effect and suspends until a handler responds:
(defn do-work [] :void
(perform (Log "starting"))
(perform (Log "done")))
Without a handler, the runtime raises an unhandled-effect error.
handle wraps a computation and provides handler clauses. k is the
continuation -- calling (resume k v) passes v back to the perform
expression and continues the computation:
(handle (do-work)
(Log [msg] k)
(do (println msg) (resume k (nil-value))))
; prints:
; starting
; done
The same computation runs under different handlers without any changes to
do-work:
(handle (do-work)
(Log [msg] k)
(do (println (str-concat "[LOG] " msg)) (resume k (nil-value))))
; prints:
; [LOG] starting
; [LOG] done
A silent handler discards all messages:
(handle (do-work)
(Log [msg] k) (resume k (nil-value)))
; prints nothing; do-work still completes
When an effect's return type is not :void, the perform expression
evaluates to whatever the handler passes to resume:
(defeffect Ask [] :int)
(defn use-ask [] :int
(+ 1 (perform (Ask))))
(handle (use-ask)
(Ask [] k) (resume k 41))
; => 42
See docs/guides/effects-system-guide.md for the full reference: effect rows,
composable handlers, and one-shot vs. cloneable continuations.
Write Turmeric code to a .tur file and load it into any REPL session with
:reload:
; hello.tur
(defn greet [name :cstr] :void
(println (str-concat "Hello, " (str-concat name "!"))))
> :reload hello.tur
reloaded hello.tur
> (greet "world")
Hello, world!
If the file contains an error, a diagnostic is printed and the session
continues. Edit the file and :reload again without restarting.
Deeper on today's topics
docs/guides/error-handling-guide.md -- panic, result-must, contract
macros (assert!, require!, ensure!)docs/guides/effects-system-guide.md -- full effects referenceConcurrency
docs/guides/threading-guide.md -- OS threads, Mutex, Atomic, channelsdocs/guides/async-await-guide.md -- async/await with fibersdocs/guides/stm-tutorial.md -- software transactional memoryType system
docs/guides/hkt-guide.md -- Functor, Monad, Applicativedocs/guides/hrt-guide.md -- rank-2 polymorphic parametersdocs/guides/gadts-guide.md -- generalized algebraic data typesdocs/guides/type-annotations-guide.md -- compound annotation syntaxModules and packages
docs/guides/module-system-guide.md -- namespacing and exportsdocs/guides/package-management-guide.md -- projects, spices, tur.lockAPI reference
docs/html/api/index.html -- generated from ;;; docstringsInteractive
docs/guides/repl-tutorial.md -- the 22-step companion to this guide; type
along in the REPL