Home Documentation Templates Examples Showcase GitHub
Theme

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.

Quick shape.

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.

When to use which.

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.