Status: Living document -- updated as idioms are established. Last Updated: 2026-05-27
MAX_FN_ARITY is 16. Functions with more than ~5 positional parameters
are a code smell; 16 is an emergency escape hatch, not a target.
defstructWhen a function needs many named, independent inputs, pack them into a struct and pass a single options value:
(defstruct CsvOpts
[delim :int ;; field separator (e.g. 44 = ',')
quote :int ;; quote char (e.g. 34 = '"')
has-header :int ;; 1 = first row is header
infer-rows :int ;; rows to sample for type inference
null-str :cstr ;; string that represents NULL (e.g. "")
])
(defn read-csv [src :cstr opts :CsvOpts] :int
...)
With currying, locking in a default options value is a one-liner:
(def default-csv-opts (CsvOpts 44 34 1 100 ""))
;; read-csv-fast is a closure with opts baked in.
;; Call it with just the filename.
(def read-csv-fast (read-csv default-csv-opts))
(read-csv-fast "data.csv")
(read-csv default-csv-opts) returns a closure (fn [src :cstr] :int ...).
No macro magic, no keyword arguments -- just currying.
& rest :typeWhen a function takes an unknown number of values of the same type
(println, format, aggregation column lists, etc.), use a variadic
rest parameter:
(defn println-all [first :cstr & rest :cstr] :void
(println first)
;; rest is a cons-list of :cstr; walk it with head/tail helpers
...)
(println-all "hello") ;; rest = nil (0)
(println-all "a" "b" "c") ;; rest = cons("b", cons("c", 0))
& rest& per parameter list -- the rest parameter must be last.& rest :int, & rest :cstr, etc.rest = 0.defn does not produce a curried entry
point. You can under-saturate up to the required positional params
(returning a variadic closure), but you cannot partially apply into
the rest slot.#{Unsafe} codeThe rest parameter is an int64_t holding a pointer to a linked list
of __tur_cons_cell { int64_t head; int64_t tail; } cells, or 0
(nil). Inline-C helpers that walk it:
(defn cons-list-sum [lst :int] #{Unsafe} :int
```c
typedef struct { int64_t head; int64_t tail; } __tur_cons_cell;
int64_t acc = 0;
__tur_cons_cell *p = (__tur_cons_cell *)(intptr_t)lst;
while (p) { acc += p->head; p = (__tur_cons_cell *)(intptr_t)p->tail; }
return acc;
```)
Or use a pure tail-recursive helper:
(defn cons-head [lst :int] #{Unsafe} :int
```c
typedef struct { int64_t head; int64_t tail; } __tur_cons_cell;
__tur_cons_cell *p = (__tur_cons_cell *)(intptr_t)lst;
return p ? p->head : 0;
```)
(defn cons-tail [lst :int] #{Unsafe} :int
```c
typedef struct { int64_t head; int64_t tail; } __tur_cons_cell;
__tur_cons_cell *p = (__tur_cons_cell *)(intptr_t)lst;
return p ? p->tail : 0;
```)
(defn list-sum-acc [lst :int acc :int] #{Unsafe} :int
(if (= lst 0)
acc
(list-sum-acc (cons-tail lst) (+ acc (cons-head lst)))))
Each variadic call with k rest args allocates k cons cells on the
heap. Variadics are for convenience calls, not hot paths. If you're
calling a variadic function in a tight loop, consider refactoring to a
fixed-arity helper.
| Situation | Reach for |
|---|---|
| >5 named, independent params | defstruct options value |
| Default values + currying | defstruct + (def fast (f defaults)) |
| Unknown number of same-type values | & rest :type variadic |
| Recursive accumulator threading context | closure-capture for context; fixed-arity for changing args |
| Genuinely >16 params | Something is wrong -- split the function |
?: empty?, done?, nil?!: assert!, push!__: __with-col-replace/: frame/select, io/read-line;;;)See the full standard in CLAUDE.md.
Quick reference:
;;; fn-name -- brief one-line summary.
;;;
;;; Parameters:
;;; param -- description
;;;
;;; Returns:
;;; description of return value
;;;
;;; Example:
;;; (fn-name arg) ; => expected result
;;;
;;; Since: Phase B1
(defn fn-name [param :int] :int
...)
Follow Clojure-style indentation -- see CLAUDE.md for the full rules.
Key points:
defn, fn, let, if, do): 2-space body indent.``` and its ) go on the same line (```)).;; Good:
(defn file-size [f] :int
```c
return (int)ftell((FILE*)f);
```)
;; Bad (closing ``` on its own line breaks Markdown fences):
(defn file-size [f] :int
```c
return (int)ftell((FILE*)f);
```
)