Language Tour

Eleven things about turmeric.

A walkthrough of what makes Turmeric tick.

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

01 Algebraic Effects 02 Typeclasses 🎯 03 ADTs & Pattern Matching 04 Delimited Continuations 05 Macro System 06 Reference Counting 07 Higher-Order Functions λ 08 Sweet-Exp Syntax 09 Contract Types 10 Spice -- Package Manager 11 Structural Typing

Effects declared.
Handlers swapped.

Your functions declare what side effects they may perform. Their callers decide how to handle them -- in production, in tests, or as mocks.

The type system ensures no effect goes unhandled. Swap between handlers at the call site without touching the core logic.

defeffect · perform · handle · resume
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))) ;; prints: computing... returns: 42 ;; Test: suppress logs, supply fixed value (handle (compute) (Ask [] k) (resume k 0) (Log [_] k) (resume k)) ;; (silent) returns: 0

Compile-time dispatch.
Zero overhead.

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

The ^Show constraint syntax expresses type requirements without sacrificing precision or performance.

defclass · definstance · ^Constraint
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"

A missing case
is a compile error.

defdata declares sum types; defgadt lets each constructor specialize its own type parameters. Every match arm refines what the checker knows -- no casts, no runtime tags.

Missing branches are compile errors, not runtime surprises. Every case is statically verified before your program runs.

defdata · defgadt · match · -Xgadt
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

Coroutines are
plain library code.

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

Generators, async/await, and backtracking are all built on those two primitives -- no special syntax, no compiler magic.

reset · shift · delimited
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.
Zero runtime cost.

Macros in Turmeric operate directly on the syntax tree at compile time. New control flow, DSLs, and syntactic sugar are defined with defmacro -- the output is indistinguishable from hand-written code.

Quasiquote (`) and unquote (~) make code templates readable. No runtime overhead, ever.

defmacro · quasiquote · unquote · splice
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"))

Freed immediately
when count hits zero.

Turmeric uses reference counting with compiler-assisted elision. rc/clone increments; rc/drop decrements. Cleanup is deterministic -- no GC pauses, no dangling pointers.

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

rc/of · rc/clone · rc/drop · rc/strong-count
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

Closures capture
their environment.

Functions are first-class values in Turmeric -- pass them, return them, store them, 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.

fn · closure · map · filter · reduce
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)

Two syntaxes,
one AST.

A single #lang declaration switches from Lisp-style parentheses to Sweet-Exp notation -- indentation-sensitive, with f(args) for inline calls and $ to avoid extra nesting.

Both syntaxes compile to the same AST. Sweet-Exp code and classic Turmeric code share libraries freely and can coexist in the same project.

#lang turmeric/neoteric · f(args)
classic.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)

Predicates live
in the type.

{ x : T | p } is a contract type -- a value of type T that satisfies predicate p, checked automatically at every call site. Name them with deftype or write them inline in any parameter or return position.

Declare invariants once in the signature with :pre and :post. The compiler inserts the checks -- you don't have to remember to. Strip them from release builds with no code changes.

{ x : T | p } · deftype · :pre · :post · -Xcontracts
contracts.tur
;; Named contract types -- predicates as types (deftype Nat { x : int | (>= x 0) }) (deftype NonZero { 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 checks at every call site ;; Requires: -Xcontracts (safe-div 10 2) ;; => 5 (safe-div 10 0) ;; contract violated: y != 0

One file.
Every dependency resolved.

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 via CPM -- no manual CMake configuration required.

defpackage · :spices · :cmake-deps
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 are structural.
Every branch checked.

(int | bool) is a structural union -- 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. Enable both with -Xunion-types.

(A | B) · any · type-of · -Xunion-types
structural.tur
;; Union types -- structural dispatch, not nominal ;; Requires: -Xunion-types (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"

Ready to try
Turmeric?

Open the playground and try any of these features right in your browser.