A tour of Turmeric

A curated list of some of Turmeric's best features

From algebraic effects to refinement types, each stop in this tour shows you real language features with real code.

Stick around to the end to learn how to install Turmeric in one step, no setup required.

01 Sweet-Exp Syntax 02 Algebraic Effects 03 Typeclasses 04 ADTs & Pattern Matching 05 Delimited Continuations 06 Macro System 07 Reference Counting 08 Optional GC 09 Higher-Order Functions 10 Refinement Types 11 Contract Types 12 Spice -- Package Manager 13 Structural Typing 14 Trowel -- The Editor

Choose your syntax to taste

A single #lang declaration switches from Lisp-style parentheses to Sweet-Exp notation. It provides a less intimidating syntax, which is indentation-sensitive, with f(args) for inline calls, among other small enhancements, while being fully backwards-compatible.

Both syntaxes compile to the same AST. Sweet-Exp code and classic Turmeric code share libraries freely and can coexist in the same project, and other #langs and "language layers" may be released in the future.

factorial.tur
;; Standard Lisp-style syntax (defn factorial [n : int] : int (if (<= n 1) 1 (* n (factorial (- n 1))))) (println (factorial 10)) ;; => 3628800 (let [xs [1 2 3 4 5]] (println (filter (fn [x] (> x 2)) xs))) ;; => (3 4 5)

Let the consumer determine how handle side-effects themselves.

Your functions declare what side effects they may perform. Their callers decide how to handle them, depending on context. For example, let behavior change in tests, for mocking, or create wizard-like workflows where the consumer handles the in-between.

The type system ensures any effect that needs handling gets handled. Swap between handlers at the call site without touching the core logic.

effects.tur
;; Declare effects as first-class operations (defeffect Ask [] : int) (defeffect Log [msg : str] : void) (defn compute [] : int (perform (Log "computing...")) (* (perform (Ask)) 2)) ;; Production: logs to stdout, asks for real value (handle (compute) (Ask [] k) (resume k 21) (Log [msg] k) (do (println msg) (resume k 0))) ;; prints: computing... returns: 42 ;; Test: suppress logs, supply fixed value (handle (compute) (Ask [] k) (resume k 0) (Log [_] k) (resume k 0)) ;; (silent) returns: 0

Like operator overloading on steroids

Typeclass dispatch resolves to a concrete instance at compile time, with no virtual tables, or additional runtime cost. Define a typeclass once and the compiler verifies every call site has a valid instance.

typeclasses.tur
(defclass Show [a] (show [x] : str)) (defdata Color (Red) (Green) (Blue)) (definstance Show [Color] (show [c] (match c (Red) "red" (Green) "green" (Blue) "blue"))) ;; ^Show means: x must have a Show instance (defn display [^Show a x] : void (println (show x))) (display (Red)) ;; => "red" (display (Blue)) ;; => "blue"

Writing data structures is made easy, while making sure nothing goes unchecked.

defdata declares sum types, while defgadt lets each constructor specialize its own type parameters. Every match arm refines what the checker knows. There's no casts, and there's no runtime tags.

Missing branches are caught at compile time. Every case is statically verified before the program runs.

expr.tur
;; GADT: each constructor carries its own return type. ;; The type-checker refines what it knows per match arm. ;; Requires: -Xgadt (defgadt Expr [a] (Lit int : (Expr int)) (Add (Expr int) (Expr int) : (Expr int)) (Mul (Expr int) (Expr int) : (Expr int))) (defn eval [e] : int (match e (Lit n) n (Add l r) (+ (eval l) (eval r)) (Mul l r) (* (eval l) (eval r)))) ;; (2 + 3) * 4 => 20 (println (eval (Mul (Add (Lit 2) (Lit 3)) (Lit 4)))) ;; => 20

Write your own flow-control primitives.

Turmeric reifies a slice of the call stack as an ordinary value. reset delimits the region and shift captures it.

Generators, async/await, and backtracking are all built on those two primitives.

gen.tur
;; Build a list lazily with shift/reset (defn yield [v] : any (shift k (cons v (k)))) (defn collect [thunk] : list (reset (do (thunk) nil))) ;; Generator that yields three values (println (collect (fn [] (yield 10) (yield 20) (yield 30)))) ;; => (10 20 30) ;; Same primitives power backtracking, ;; async/await, and cooperative scheduling.

Compile-time transforms without losing type safety.

Macros in Turmeric operate directly on the syntax tree at compile time. New control flow, DSLs, and syntactic sugar are defined with defmacro, and a macro may generate any valid type-safe Turmeric code.

Quasiquote (`) and unquote (~) is a lightweight syntax that will hopefully feel familiar to Clojurians, at least.

macros.tur
;; `unless` -- inverse of when (defmacro unless [test & body] `(when (not ~test) ~@body)) ;; multi-branch conditional -- `cond` (defmacro cond [& clauses] (if (nil? clauses) nil `(if ~(first clauses) ~(second clauses) (cond ~@(rest (rest clauses))))) ;; Usage: clean multi-branch dispatch (defn sign [n : int] : str (cond (< n 0) "negative" (= n 0) "zero" (> n 0) "positive"))

Optional reference counting means less manual memory bookkeeping.

Turmeric uses reference counting with compiler-assisted elision. rc/clone increments, while rc/drop decrements. Cleanup is deterministic, and there are never any GC pauses or dangling pointers.

When the count hits zero, the value is freed right there, at that line. You can see exactly where memory is released.

memory.tur
;; Heap-allocate a value with rc/of (let [data (rc/of 42)] (println (rc/strong-count data)) ;; => 1 ;; Clone increments the reference count (let [alias (rc/clone data)] (println (rc/strong-count data)) ;; => 2 (println (= @data @alias)) ;; => true (rc/drop alias)) ;; count -> 1 (println (rc/strong-count data)) ;; => 1 (rc/drop data)) ;; freed here, no GC

Garbage collection, but only when you want it.

Reference counting leaks cycles, and every RC language has this problem. Turmeric's answer is a Bacon-Rajan trial-deletion collector layered on top of RC, and it is off by default. Nothing traces, nothing pauses, until you ask.

Turn it on and you choose the trigger: (gc!) to collect right here, a suspect-count threshold, or fully automatic at allocation checkpoints. Only rc<T> values participate -- stack values, arenas, and by-value structs are never involved.

cycles.tur
;; Two nodes pointing at each other: RC alone ;; can never free this -- both counts stay at 1. (defstruct Node [next : rc<Node>]) ;; Default: GC_DISABLED. RC does all the work, ;; cycles simply accumulate. Zero overhead. (make-cycle!) (println (gc-live-blocks)) ;; => 2 ;; Opt in, then collect on demand (gc-enable!) (gc!) (println (gc-objects-freed)) ;; => 2 (println (gc-live-blocks)) ;; => 0 ;; ...or hand the timing over entirely. ;; Opt-in, always: nothing collects until you call this. (gc-auto!) ;; Back to no collector at all, any time (gc-disable!)

Create functions with context: Closures.

Functions are first-class values in Turmeric, and you can pass them, return them, store them, and compose them. Closures capture their lexical environment with full type inference.

The standard library's map, filter, and reduce work uniformly over any foldable structure, not just lists.

closures.tur
;; Closures capture their lexical environment (defn make-adder [n : int] : (fn [int] int) (fn [x : int] (+ n x))) (let [add5 (make-adder 5) add10 (make-adder 10)] (println (add5 3)) ;; => 8 (println (add10 7))) ;; => 17 ;; Pipeline transformations with map + filter (let [nums [1 2 3 4 5]] (println (map (fn [x] (* x x)) (filter (fn [x] (= (% x 2) 0)) nums)))) ;; => (4 16)

Check values at compile-time when possible…

#refine{ x : T | p } is a refinement type -- a value of type T that satisfies predicate p, checked automatically at compile-time (if possible). A parameter's predicate becomes a hypothesis, and a return type becomes a goal, so a bad argument is a compile error.

There is no external solver to install. The solver is built into compiler, so it works in the browser playground too.

refined.tur
;; Proved: x > 0 entails 2x > 0. ;; No runtime check is emitted for the return. (defn double-pos [x : #refine{ v : int | (> v 0) }] : #refine{ r : int | (> r 0) } (* x 2)) ;; Results carry their refinements, so proofs compose (defn twice [y : Pos] : int (* y 2)) (twice (double-pos p)) ;; proved ;; A bad argument is a COMPILE error, not a panic (safe-div 10 0) ;; error[TUR-E0371]: refinement on argument 2 ;; note: (not= x 0) is false for the value given here ;; Can't prove it? You get the check, and a hint. ;; help: (< i n) would discharge it

…and check values at run-time when needed.

The same #refine{...} predicate has a runtime meaning too. Name one with deftype, or declare invariants directly in the signature with :pre and :post, and the compiler inserts the checks for you.

The runtime meaning is what keeps the prover honest: it can give up on an obligation and stay sound, falling back to the check the value would have had anyway. Turning it on can never make a correct program wrong, and you can strip the checks from a release build with no code changes. Refinements and contracts are optional.

contracts.tur
;; Named contract types -- predicates as types (deftype Nat #refine{ x : int | (>= x 0) }) (deftype NonZero #refine{ x : int | (!= x 0) }) ;; Predicates live in the signature, not the body (defn safe-div [x : Nat y : NonZero] : Nat :post (= (* result y) x) (/ x y)) ;; Compiler inserts the checks -- always on, no flag. ;; Strip them from a release with --no-contracts. (safe-div 10 2) ;; => 5 (safe-div 10 0) ;; contract violated: y != 0

One file tracks your project's dependencies.

One build.tur file describes your package and all its spices -- Turmeric fetches, builds, and links them automatically. Git URLs and version refs are all you need.

C and CMake dependencies go under :cmake-deps and are wired in automatically via CPM.cmake.

build.tur
(defpackage my-app :name "my-app" :version "0.1.0" :entry "src/main.tur" ;; Turmeric dependencies -- called spices :spices { "json" {:url "https://github.com/alice/tur-json" :ref "v1.2.0"} "http" {:url "https://github.com/bob/tur-http" :ref "v3.0.0"} } ;; C/CMake dependencies (CPM-compatible) :cmake-deps { "sqlite3" {:url "https://github.com/sqlite/sqlite" :ref "version-3.45.0"} "raylib" {:url "https://github.com/raysan5/raylib" :ref "5.0"} })

Union types make sure every branch of a conditional is covered.

(int | bool) is a structural union, for which the compiler generates exhaustiveness-checked dispatch for every member with no boxing or runtime overhead. Every union member must be handled.

The any top type enables gradual typing: every concrete type is a subtype of any. Inspect the tag at runtime with type-of. Both are built right into the language.

structural.tur
;; Union types -- structural dispatch, not nominal (defn describe [x : (int | bool)] : int (match x (n : int) (do (println n) 0) (b : bool) (do (println b) 0))) (describe 42) ;; => 42 (describe true) ;; => true ;; Typeclass dispatch through a union -- ;; compiler generates the tag-dispatch call (defn print-any [x : (int | bool)] : int (println (.show x)) 0) ;; `any` top type -- gradual typing (defn show-type [x : any] : int (println (type-of x)) 0) (show-type 42) ;; => "int" (show-type "hello") ;; => "cstr"

One download, with nothing else to install.

Trowel is a fast, native desktop editor for Turmeric -- not Electron! -- with the compiler shipped inside the bundle. Open a file, press Run, see the output in the pane below. A live REPL sits in the split underneath your code.

Docstrings and completion for stdlib and buffer-local names come from the bundled compiler's own doc index, so they always match the compiler you are building with. Toggle between languages from the toolbar, or sccaffold a new project without touching the terminal.

The Trowel editor showing lens-example.tur in a tabbed editor above a live REPL pane, where :run has printed 'Updated boss name: Alice'

Ready to try Turmeric?

Open the playground and try any of these features right in your browser -- or download Trowel and build them locally.