a tutorial
Learn wick in 10 minutes
Ten short steps. Each has a code box you can edit and run. Each step has its own fresh wick environment, so changes in one step don't leak into the next. The full REPL is one click away when you outgrow the tutorial.
1. Hello
A Lisp expression is a list, and the first thing in the list is the operator. (+ 1 2) means "call + with 1 and 2." That's the whole syntax. Once you see it, you see it everywhere.
2. Names
def binds a value to a name. After the binding, the name itself is an expression that returns the value. Code runs top to bottom, so def happens before *.
3. Functions
fn makes a function. (fn (n) (* n n)) reads as "a function of n that returns n times n." Combine with def to give it a name, then call it like any other operator.
4. Lists
Lists are the bones of Lisp. list makes one. car returns the first element; cdr returns everything after. cons puts a new element on the front. (The names are old. They predate "head" and "tail.")
5. Map and range
Functions are values. They can be passed to other functions. map takes a function and a list and returns the list of results. range generates lists of integers, which means you rarely have to write a loop.
6. If, and everything as an expression
if picks one branch or the other and returns its value. There is no statement-vs-expression split here — if is a value, just like a number. cond is a chain of ifs for when one isn't enough.
7. Recursion
A function can call itself. Factorial is the canonical demonstration. wick has tail-call optimization, which means recursive calls in the tail position don't grow the stack — the count-down at the end runs a hundred thousand iterations and returns cleanly.
8. Closures
When a function references a variable from the surrounding scope, it captures that variable — the function carries its own little environment with it. make-counter below produces a fresh counter every time it's called, each one with its own private n.
9. Dicts
Lists are the bones; dicts are how you carry named data. (dict "k1" v1 "k2" v2) builds one — or shorter, {"k1" v1 "k2" v2}, which the reader desugars to the same call. [a b c] is the matching shorthand for (list a b c). dict-get reads a key (with an optional default for missing keys); dict-set returns a new dict with the key added or replaced — the original is untouched. Same shape as cons with lists: every change makes a new value, the old one stays as it was.
10. That’s wick
That's the whole language, more or less. Special forms (quote, if, cond, def, set!, fn, let, begin, and, or, try), a small set of primitives (arithmetic and comparison, cons, car, cdr, list, null?, pair?, eq?, not, apply, print, display, newline, mod, string-length, string-append, number->string, string->number, the string-processing family string-contains? / string-split / string-replace / substring / string-upcase / string-downcase / string-trim / string-join, the regex family re-match? / re-find / re-find-all / re-replace / re-split, the dict family dict / dict-get / dict-set / dict-del / dict-has? / dict-keys / dict-values / dict-size / dict?, the error family raise / error? / error-message for catching things try wraps, and json-parse / json-stringify for round-tripping data through JSON), and a stdlib written in wick itself (map, filter, fold, reverse, range, length, sum, product, take, drop, take-while, drop-while, nth, last, append, inc, dec, zero?, even?, odd?, abs, min, max, member?, find, any?, all?, sort, for-each). The Go build also has read-file / write-file / append-file / file-exists? for disk, and http-get / http-post for fetching and sending to the world (each returns a dict with status, body, and headers; both take an optional headers dict for auth or content-type; raises on network error so you can try it). The full REPL is here when you want to keep going, and the reference page lists every form with one-line descriptions. Source: github.com/pw/Wick.