Core language · First-class behaviour
Lambdas & closures.
Nift lambdas are first-class callable values: define behaviour inline, pass it to collection operations, store it, return it from a function, and close over live lexical state.
Use x => expression for one parameter, (a, b) => expression for several, () => expression for none, or a block body when the callback needs statements and control flow.
Expression lambdas
An expression lambda evaluates its body when called and returns that value. Parentheses around a single parameter are optional.
$[double := x => x * 2]
$[add := (a, b) => a + b]
$[answer := () => 42]
$[double(6)]
$[add(2, 3)]
$[answer()] Block lambdas
Use a statement block when the operation needs local bindings, conditions, loops or an explicit return. Block lambdas execute the same statement machinery as functions.
$[describe := score => {
if(score >= 90) { return "excellent" }
if(score >= 50) { return "pass" }
return "retry"
}]
$[describe(84)] Collection callbacks
The most common use is to put behaviour next to the data operation that consumes it. Arrays and collections accept lambdas for higher-order operations such as map, filter, reduce, any, all, find, count, sort_by and group_by.
$[posts := @inject("data/posts.json")]
$[published := posts.filter(post => !post.draft)]
$[titles := published.map(post => post.title)]
$[total_words := posts.map(post => post.words).sum()]
$[by_year := posts.group_by(post => post.year)]
$[first_featured := posts.find(post => post.featured)] Callbacks over map-like collections receive (key, value). reduce receives the accumulator and current value and starts from an explicit initial value.
$[total := [1, 2, 3, 4].reduce((acc, value) => acc + value, 0)]
$[visible := settings.filter((key, value) => value.enabled)] Named functions are callables too
Lambdas are not a callback-only feature. Named functions are first-class callable values, so APIs that accept a lambda can also accept an existing function.
@fn(is_public(post)) {
return post.published && !post.draft
}
$[predicate := is_public]
$[public_posts := posts.filter(predicate)] Callables can also be passed to your own functions.
@fn(apply(value, operation)) {
return operation(value)
}
$[triple := x => x * 3]
$[apply(7, triple)] Closures capture live bindings
A lambda closes over lexical bindings, not a frozen copy of their current values. Rebinding captured state is therefore visible to the closure.
$[factor := 10]
$[multiply := x => x * factor]
$[multiply(2)]
$[factor = 20]
$[multiply(2)] Escaping closures keep state alive
Bindings captured by a returned lambda remain alive after the defining function returns. Each call to counter below creates independent state.
@fn(counter(start)) {
n := start
return () => n++
}
$[a := counter(10)]
$[b := counter(100)]
$[a()]
$[a()]
$[b()] Function factories
@fn(at_least(minimum)) {
return value => value >= minimum
}
$[passing := at_least(50)]
$[distinctions := at_least(75)]
$[scores.filter(passing)]
$[scores.filter(distinctions)] Variadic lambdas and spread
A lambda may end its parameter list with one variadic parameter. The remaining arguments become an ordinary heterogeneous array. Spread expands an array back into positional arguments.
$[collect := (first, ...rest) => {
return {"first": first, "rest": rest}
}]
$[collect("a", "b", "c")]
$[values := [2, 3, 4]]
$[sum3 := (a, b, c) => a + b + c]
$[sum3(...values)] Only one variadic parameter is allowed, it must be last, and spread requires an array.
Rendered block lambdas
A markup-first block lambda can map structured values directly to rendered text—useful for frontend generation before join("").
$[cards := posts
.filter(post => post.published)
.map(post => {
<article class="card">
<h2>$[post.title]</h2>
<p>$[post.summary]</p>
</article>
})
.join("")]
$[cards] A markup-first block produces its rendered text as the callback value. An ordinary statement-block lambda follows normal return semantics.
Callable identity
Callables are identity-bearing values. Use same(a, b) to ask whether two bindings refer to the same callable. Separately-created lambdas are separate instances even when their source is identical.
$[f := x => x * 2]
$[alias := f]
$[other := x => x * 2]
$[same(f, alias)]
$[same(f, other)] Closures exported from scripts
Imported scripts have isolated scope, but an exported closure can retain private bindings it captured. This supports module-style APIs with encapsulated state.
count := 0
next := () => count++
export(next) @import("counter.f")
$[next()]
$[next()] See Imports & exports for module and export rules.
Use a named @fn when behaviour deserves a stable name or broad reuse. Use a lambda when behaviour is naturally local to a transformation, callback, factory or closure. Both are first-class callables.