# Functions Functions are the primary unit of abstraction in Deft. This page covers the `def` form, parameter syntax, type hints, closures, and the `@doc`/meta-tag decorators. ## Defining Functions Use `def` with the body enclosed in braces. Parameters are declared inside `|...|` at the start of the body. Use `||` for zero params.
 def greet {|name^string|
    @doc "Print a greeting to stdout."
    echo "Hello, $name!"
}

def magic-number {|| 42 }

greet "world"               # => Hello, world!
echo [magic-number]         # => 42 
The body is one or more statements; the value of the last statement is the return value. There's an implicit `return` — use explicit `return` only for early exits:
 def fib {|n^number|
    if ($n < 2) { return $n }
    [$fib ($n - 1)] + [$fib ($n - 2)]
}

echo [fib 10]               # => 55 
## Parameter Syntax Parameters live inside `|...|`. They're comma- or whitespace-separated; either works.
 def add       {|a b|        ($a + $b) }
def add-also  {|a, b|       ($a + $b) }
def variadic  {|first @rest| $rest }       # variadic via @ 
### Variadic functions `@rest` (or any `@name`) collects remaining args into a list. The `@` mirrors the `@{...}` vector literal — it signals "list-collecting param". Must be last; the name after `@` is bound verbatim:
 def sum-all {|@nums|
    @doc "Sum any number of arguments."
    reduce $nums 0 { ($acc + $it) }
}

echo [sum-all 1 2 3 4]      # => 10 
### Default values Parameters may have defaults with `=value`:
 def greet {|name="World"| echo "Hello, $name!" }
greet                # => Hello, World!
greet "Alice"        # => Hello, Alice! 
## Type Hints Type hints follow the parameter name with a caret (`^`). They serve three purposes: 1. **Documentation** — communicate expected types to readers. 2. **Validation** — runtime type checking on call entry. 3. **Editor support** — power autocomplete and signature help.
 def distance {|p^Point|
    [math/sqrt (($p~x * $p~x) + ($p~y * $p~y))]
}

def greet {|name^string count^number|
    for i [range $count] {
        echo "Hello, $name!"
    }
} 
Type hints do **not** enforce static typing — Deft remains dynamically typed. They are "hints" that improve tooling and trigger validation at specific boundaries (function entry, typed `set`). ### Primitive types | Hint | Description | Examples | |---|---|---| | `string` | Text | `"hi"`, `'world'` | | `number` | Numeric | `42`, `3.14` | | `boolean` | Boolean | `true`, `false` | | `bool` | Alias for boolean | `true`, `false` | | `nil` | Null | `nil` | | `list` | List/array | `@{ 1 2 3 }` | | `map` | Key-value map | `%{ a 1 b 2 }` | | `set` | Set | `^{ 1 2 3 }` | | `keyword` | Keyword | `:foo` | ### User-defined types Hints can reference any `deftype`:
 deftype User %{
    :name %{:type :string :required true}
    :email %{:type :string}
    :age %{:type :number}
}

def display {|u^User| echo "$u~name <$u~email>" }

def alice %User{ :name "Alice" :email "alice@example.com" :age 30 }
display $alice 
See [Types and Protocols](types-and-protocols) for the full `deftype` schema reference. ### Runtime validation Hints trigger validation at call entry:
 def greet {|name^string| echo "Hello, $name!" }

greet "Alice"     # ✓
greet 42          # ✗ Error: type error in 'greet': parameter 'name' expected string, got number 
Untyped parameters accept any value, so you can mix freely:
 def flexible {|typed^string untyped|
    # $typed is validated at call time
    # $untyped can be anything
} 
### Typed `set` `set` can carry a type hint that validates the value:
 set name^string "Alice"
set count^number 42
set user^User [create-user "Bob"] 
### Return-type annotations Can be provided on the function name ...
 def fetch-user^string {|id^string|
    # ...
}
} 
## Named Optional Arguments (`--flag` kwargs) When a parameter carries a `deftype` hint (any non-primitive `^TypeName`), Deft treats it as a **flag-collection parameter**. Callers pass `--flag value` pairs instead of positionally binding that argument; primitive-typed params in front of it stay positional. This is the idiomatic way to express optional, named arguments — the parameter type's schema is the contract.
 deftype BuildOpts %{
    :push    %{:type :boolean :default false}
    :nocache %{:type :boolean :default false}
    :tag     %{:type :string}
}

def build {|service^string opts^BuildOpts|
    echo "building $service"
    if $opts~push    { echo "  will push" }
    if $opts~nocache { echo "  no cache" }
    if ($opts~?tag)  { echo "  tag: $opts~tag" }
}

build "myapp" --push --tag latest       # => building myapp / will push / tag: latest
build "myapp" --nocache                 # => building myapp / no cache
build "myapp"                          # => building myapp   (opts omitted entirely) 
### How it works - A parameter whose hint is a user-defined type turns that slot into a **flag-collection parameter**. It must be the **last** parameter — everything before it is positional and required; nothing may come after it. - At the call site, anything after the positional prefix is parsed as `--flag value` pairs and assembled into a `%TypeName{...}` instance using the same rules as the kwarg type constructor (see [Types and Protocols](types-and-protocols)). - Boolean fields accept a bare `--flag` shortcut meaning `true` (`--push` is equivalent to `--push true`). - Values are type-checked against the schema's `:type`; mismatches raise at the call site. - The flag-collection parameter may be **omitted entirely** at the call site — the value defaults to a fresh instance with all `:default` values applied (and `nil` for fields without a default). ### Reading flag values Use `~field` for fields that always have a value (defaulted or required), and `~?field` when you need to distinguish "not given" from a falsy default:
 if $opts~push { ... }                 # bool field — false if defaulted
if ($opts~?tag) != nil { ... }        # was --tag passed at all? 
`$m~?field` returns `nil` when the field is absent (null-propagating, safe to chain); `$m~field` is the plain accessor. ### The flag-collection parameter must be last Only one flag-collection slot is allowed per function, and it must be the **trailing** parameter. Internally, the runtime collects the `--flag value` pairs and appends the resulting `%TypeName{...}` instance as the final positional argument — so any primitive parameter declared after it would never receive a value, and the call raises at runtime. The canonical signature is therefore `positional... opts^Opts`:
 def deploy {|app^string env^string opts^DeployOpts|
    # ...
}

deploy "billing" "prod" --replicas 5 --dry-run

# ✗ invalid — deftype parameter is not last
def broken {|opts^Opts app^string| ... } 
See [Types and Protocols](types-and-protocols) (› Kwarg construction) for the parallel feature on type constructors (`[User --name X --dept Y]`). ## Closures Anonymous functions use the same `|params| body` syntax without `def`:
 def square {|x| ($x * $x) }
set also-square {|x| ($x * $x) }       # assign closure to a variable

[map $xs { ($it * 2) }]                # implicit $it param
[map $xs { |x| ($x * 2) }]             # explicit param 
### Implicit `$it` A closure with no parameter list binds the argument to `$it`:
 @{ 1 2 3 } |> map { $it * 2 }          # $it is 1, then 2, then 3
$xs |> filter { ($it % 2) == 0 } 
For multi-arg use, or when you want a clearer name, use explicit params:
 @{ 1 2 3 } |> reduce 0 { |acc x| ($acc + $x) } 
### Captured locals Closures capture enclosing locals by reference. Mutations inside the closure propagate back to the enclosing scope:
 def counter {||
    set count 0
    {||
        incr count
        $count
    
    }
}

set bump $counter
echo [$bump]                # => 1
echo [$bump]                # => 2 
This works for `<<`, `set`, and `incr` inside any inner closure (`each`, `map`, `filter`, `defer`, etc.). The capture is boxed once at function entry — see [Syntax and Style](syntax-and-style) for the performance note. ## Multi-Arity Dispatch A function can have multiple bodies selected by arity. Each arity lives in its own `{|...| ...}` block:
 def range {
    {|n| [range 0 $n] }
    {|start end| [range $start $end] }
}

[range 5]                   # => @{0 1 2 3 4}
[range 2 5]                 # => @{2 3 4} 
This is the canonical pattern for arity-overloaded stdlib functions like `map`, `filter`, `take`, `drop`, etc. — see [Lazy Sequences](lazy-sequences) and [Transducers](transducers). ## Decorators (`@`-prefixed metadata) A function body can carry `@`-prefixed meta tags. They attach metadata to the function that the runtime, tooling, and your own code can read back via `meta` / `info/*` (see [Metaprogramming](metaprogramming)). ### Reserved meta names A small set of decorator names have conventional meaning. Tooling (signature display, help bars, doc renderers) reads these specifically: | Decorator | Purpose | Value form | |---|---|---| | `@doc` | Function-level documentation string | string (canonical) | | `@returns` | Return-type annotation (mirrors `def name^RetType`) | string or keyword | | `@since` | Version when the function was introduced | string | | `@deprecated` | Mark the function as deprecated | boolean or string |
 def distance {|p^Point|
    @doc "Euclidean distance from origin."
    @returns :number
    [math/sqrt (($p~x * $p~x) + ($p~y * $p~y))]
}

def old-helper {|x|
    @doc "Use `helper` instead."
    @deprecated true
    @since "0.3.0"
    # ...
} 
`@returns` and the syntactic `def name^RetType {|...|}` form populate the same `"returns"` key in metadata — use whichever reads better. Retrieve at runtime via `meta`:
 echo [meta $distance "doc"]               # => "Euclidean distance from origin."
echo [meta $distance "returns"]           # => :number 
### Per-parameter docs (`@`) A decorator whose name matches a declared parameter attaches its value as that parameter's `:description` in the function's signature metadata (read by `info/sig`, tooltip renderers, and the TUI help bar). The param-name match is exact.
 def greet {|name^string times^number|
    @doc "Print a greeting N times."
    @name "The user's name (required)."
    @times "How many times to print (defaults to 1)."
    for i [range $times] { echo "Hello, $name!" }
}

echo [info/sig "greet"]
# => greet(name^string, times^number) | Print a greeting N times. 
Only the **string** form populates the param's `:description` slot. Non-string values under a param-name key still attach to the function's meta map (under that key), but don't become a param description. ### Custom tags Any other `@key value` is caller-controlled metadata — use it for whatever your program or tooling needs. Values may be any literal form (strings, numbers, booleans, keywords, maps, sets, lists, tagged maps); the materialiser converts them to the appropriate runtime Value:
 def handler {|req|
    @doc "Handle an HTTP request."
    @auth-required true
    @category :experimental
    @since 3
    @tags @{"v1" "stable"}
    @config %{ :timeout 30 :retries 3 }
    # ...
}

echo [meta $handler "auth-required"]      # => true
echo [meta $handler "since"]              # => 3
echo [meta $handler "tags"](0)            # => "v1"
echo [meta $handler "config"]~timeout     # => 30 
Repeated keys overwrite — later values win. Function calls and variable references cannot appear as decorator values (they aren't evaluated at compile time and are silently dropped); stick to literals. For the full metadata map on a value, use `info/props`:
 echo [info/props $handler]                # => full metadata map 
### RPC exposure (`@rpc`) The `@rpc true` tag publishes a function to the runtime's `RpcRegistry` so other runtimes can call it via `rpc`. Available in every host — see [RPC](rpc).
 def cmd-open-file {|path^string|
    @rpc true
    @doc "Open a file in this editor panel."
    # ...
} 
### Async functions (`@async`) The `@async` decorator turns a `def` into an asynchronous function. Calling it returns a `Future` immediately — the body runs as a coroutine on the runtime's event loop. The caller resolves the result with `await`:
 def fetch-data {|url^string|
    @async
    @doc "Fetch a URL concurrently; resolves to the response body."
    set resp [http/get $url]
    return $resp~body
}

set fut [fetch-data "https://api.example.com/data"]
echo $fut                          # => <future:pending>

set body [await $fut]              # suspends the current coroutine until done
echo $body 
`await` on a Future: - returns immediately if the Future is already resolved, - suspends the current coroutine (not the whole runtime) if pending, and resumes when the Future resolves, - passes non-Future values through unchanged, so it's safe to sprinkle on values that might or might not be async. ### The body is a coroutine Inside an `@async` function, you can `await` other Futures and `yield` control to the event loop. This is what makes I/O concurrency ergonomic — sequential-looking code that actually runs concurrently:
 def aggregate {|urls^list|
    @async
    set bodies @{}
    each { |u|
        $bodies << [await [fetch-data $u]]      # sequential awaits
    } $urls
    $bodies
} 
For wide parallelism (all requests in flight at once), pair `@async` with explicit task spawning — see [Concurrency](concurrency). ### Calling rules - Calling an `@async` function **never blocks** — the call returns a Future synchronously after the coroutine's first suspension point. - A function with `@async` is itself a regular function value; it can be passed to `map`, stored in a map, called via `apply`, etc. The `@async` only affects what calling it *returns*. - The decorator works on multi-arity bodies as well — each arity becomes its own async closure. - Errors inside the body reject the Future rather than propagating synchronously; `await` re-throws them at the call site.
 def risky {|x^number|
    @async
    if ($x == 0) { [throw "div by zero"] }
    ($x / $x)
}

try {
    [await [risky 0]]               # re-throws here, not at [risky 0]
} catch e {
    echo "caught: $e"
} 
### Generators (`@generator`) The `@generator` decorator compiles the function body as a **coroutine generator** — the same machinery as `defcoro`. Calling the function does **not** run the body: it returns a `` object immediately, suspended before the first statement, with the arguments already bound. Each `yield` in the body hands one value to the caller; drive the generator with `resume` (or its alias `next`):
 def range-from {|start^number|
    @generator
    @doc "Yield start, start+1, start+2, ..."
    set n $start
    while true {
        yield $n
        incr n
    }
}

set g [range-from 5]           # => <coroutine> — body hasn't run yet
[coro-state $g]                # => :ready

[next $g]                      # => 5
[next $g]                      # => 6
[resume $g]                    # => 7   (resume and next are the same) 
Notes: - Calling a generator function never executes the body — it only allocates the coroutine and binds the arguments. - `yield value` suspends the body and returns `value` to the caller; a bare `yield` returns `nil`. - When the body runs to completion, the next `resume`/`next` returns `nil` and `[coro-state $g]` becomes `:done`. `resume` on a finished generator keeps returning `nil` rather than erroring. - States are the coroutine states: `:ready` (created, not started), `:suspended` (between yields), `:done`, plus `:running` and `:failed`. If the body throws, the error is logged (`CORO ERROR`) at the resume site. - All the usual `def` parameter syntax works — type hints, defaults, rest params — and the generator closure itself is a regular function value (storable, passable to `apply`, etc.). - The standalone `defcoro` form is the same thing without the `def` wrapper; `spawn` + `vwait` drive coroutines on the runtime scheduler (see [Concurrency](concurrency)). ### Related - [Concurrency](concurrency) — `deftask`, `defcoro`, `after`, `sleep`, and the runtime scheduler model. - `await`, `defcoro`, `yield` — see the in-repl `help` docs for the per-stdlib function reference. ## Function Values Functions are first-class values. Pass them as arguments, store them in data structures, invoke them via `apply` or just by position:
 def double {|x| ($x * 2) }

[map $xs $double]                          # pass function value
[map $xs double]                            # bare name also works
[apply $double @{5}]                       # => 10 
### `IFn` — callable values A fixed set of built-in value kinds can be called like a function: keywords, maps, tagged maps, sets, and vectors:
 def lookup %{ :name "Alice" :role :admin }
echo [$lookup :name]                       # => Alice — map called with key

def users %{ :alice %{ :role :admin } :bob %{ :role :user } }
echo [$users :alice]                       # => %{ :role :admin }

echo [:name $lookup]                       # => Alice — keyword called on map 
This is built-in dispatch, not a user-extensible protocol: `IFn` callability is fixed to the kinds above, so a `defimpl` on `IFn` for a user-defined type is never consulted. User-defined type instances are only callable in the generic tagged-map sense — `[$user :name]` reads a field. To attach custom behavior to your own type, implement one of the regular protocols via `defimpl` and call the method explicitly — see [Types and Protocols](types-and-protocols). ### Partial application with `curry` `curry` fixes leading arguments, returning a closure that awaits the rest. When you call the result, the fixed args are prepended to whatever you pass:
 def add {|a b| ($a + $b) }

set inc [curry $add 1]            # fix the first arg
echo [$inc 41]                    # => 42  (calls add 1 41)

set ten [curry $add 10]
echo [$ten 5]                     # => 15 
`curry` follows the same resolution rules as `apply`, so you can pass a string/keyword name in place of a function value:
 set greet-bob [curry "greet" "Bob"]
$greet-bob                        # calls (greet "Bob") 
The returned closure is itself a regular function value — it can be curried again, passed to `map`/`filter`, or stored in a data structure. ## Recursion and Tail Calls Tail calls are compiled to in-place loops, so recursive idioms are safe at any depth:
 def count-down {|n^number|
    echo $n
    if ($n > 0) { count-down ($n - 1) }
}

count-down 1000000                  # does not overflow 
For non-tail recursion that would blow the stack, use an explicit accumulator or refactor to a loop. Non-tail recursion that isn't refactored is bounded by `MAX_CALL_DEPTH` (default 4096 frames) — the in-place-loop optimisation applies only to self-recursive tail calls. ## Closures vs Methods Deft doesn't have method dispatch on the first argument the way Clojure does. To attach behaviour to a type, use `defimpl` with a protocol — see [Types and Protocols](types-and-protocols).
 deftype Point %{ :x %{} :y %{} }

defimpl Point Drawable {
    draw {|ctx bounds| [draw/cell $ctx $self~x $self~y "*"] }
}

defimpl Point Measurable {
    measure {|| %{ :w 1 :h 1 } }
} 
`$self` inside an impl body refers to the receiver.