Turmeric supports Haskell-style currying: every N-argument function is also a valid
(N-1)-argument function that returns a closure waiting for the remaining argument.
There is no special syntax — supply fewer arguments than declared and the elaborator
synthesizes a partial-application closure for you.
| Expression | f type |
Result type | Behavior |
|---|---|---|---|
(f a) |
(-> A B) |
B |
fully saturated |
(f a) |
(-> A B C) |
(-> B C) |
partial application |
(f a b) |
(-> A B C) |
C |
fully saturated |
(f a b c) |
(-> A B C) where C = (-> D E) |
E |
over-application |
((f a) b) |
(-> A B C) |
C |
explicit two-step, same as (f a b) |
Supply fewer arguments than the function's arity; the result is a closure waiting for the rest.
(defn add [a : int b : int] : int
(+ a b))
(let [inc (add 1)]
(inc 41)) ; => 42
The same works for built-in operators:
;; Operator section -- (+ 1) is a unary increment.
(map (+ 1) xs)
;; Fold with a partial-applied combiner.
(fold (+ 0) xs)
The partially-applied closure carries the full type of its remaining parameters,
including type annotations on rank-2 polymorphic slots and effect rows. A
partial application of an #fx{IO} function is itself #fx{IO}.
When a function returns another function, you can chain calls inline:
(defn make-adder [n : int] : (fn [int] int)
(fn [x : int] : int (+ n x)))
(make-adder 10 5) ; => 15, equivalent to ((make-adder 10) 5)
The elaborator detects that the intermediate result is callable and threads the remaining arguments into it. Chains of arbitrary depth work the same way.
Over-applying past a non-function return type is a type error:
(add 1 2 3)
; TUR-E0002: function returns int, which is not callable --
; did you mean to pass all 2 arguments?
curry macroFor cases where explicit currying syntax reads better than partial application,
stdlib/macros.tur provides:
;;; curry -- convert a 2-argument function to its curried form.
(defmacro curry [f]
`(fn [a] (fn [b] (~f a b))))
Usage:
(defn add [a : int b : int] : int (+ a b))
(let [add1 ((curry add) 1)]
(add1 41)) ; => 42
Most of the time you don't need curry — direct partial application ((add 1))
is shorter and produces the same closure.
Turmeric uses the worker/wrapper model (the same approach as OCaml and MLton), not GHC-style full currying:
So (add 1) allocates a small heap closure that remembers 1, but (add 1 2)
compiles straight to a C call with two arguments. The only function type in the
type system is (-> ...); partial-application closures share it with everything else.
Variadic defn is not auto-curried. A function declared with & rest does
not produce a curried entry point. You can under-saturate up to the fixed
positional params (returning a variadic closure), but you cannot partially
apply into the rest slot. See Function Arity Style Guide.
MAX_FN_ARITY still applies. The remaining-parameter list of any partial
application is a subset of the original arity, so the 16-arg cap is automatically
satisfied — but if your function is already near the cap, the wrapper inherits it.
One heap allocation per partial. Each under-saturated call site allocates one closure. For tight inner loops, hoist the partial application outside the loop or call the function fully-saturated.
Type inference is local. When using (curry f) or writing your own wrapper,
the elaborator needs enough surrounding context to fix the result type. If you
see TUR-E0001 on a curried call, ascribe the intended type at the binding site.
let, fn, defn semanticsstdlib/macros.tur — the curry macro source