ZE-016 - Unified Function and Closure Syntax
Unified Function and Closure Syntax
This proposal has been accepted and implemented. You can use this feature since Zirric v0.1.0.
Introduction
This proposal replaces the brace-arrow closure syntax { args -> body } with a fn-based syntax fn(args) { body }, making closures and named functions share the same structural shape. All other shorthand forms for function literals are discontinued. This mostly affects function declarations.
Motivation
The current closure syntax { a, b -> a + b } differs significantly from named function declarations fn add(a, b) { ... }. This creates two distinct mental models for the same concept and introduces ambiguity with Dict literals (both start with {).
The new unified syntax:
- Reduces cognitive load: one syntactic pattern for all functions.
- Removes parser ambiguity:
{always introduces a block, never a function literal. - Improves readability: closures are immediately recognisable by the
fnkeyword. - Simplifies the grammar: a single production covers both named and anonymous functions.
- Feels more consistent: Zirric prefers keywords over punctuation and a minimal syntax set.
Proposed Solution
Named functions (unchanged shape)
fn add(a, b) {
return a + b
}
Named functions in literal syntax (removed)
fn add (a,b) {
return a + b
}
Anonymous functions (closures) — new syntax
const add = fn(a, b) {
a + b
}
constconstconstconstconstconstt = fn(name) {
"Hello, " + name
}
Zero-parameter closures
constconstconstconstconstconstk = fn() {
42
}
Discontinued syntax
The following forms are removed:
| Old form | Replacement |
|---|---|
{ a, b -> a + b } |
fn(a, b) { a + b } |
{ -> expr } |
fn() { expr } |
{ expr } (implicit no-arg) |
fn() { expr } |
Summary of the unified grammar
decl_fn = "fn", identifier, "(", [ parameter_list ], ")", block ;
fn_literal = "fn", "(", [ parameter_list ], ")", block ;
parameter_list = parameter, { ",", parameter } ;
parameter = [ attr_chain ], identifier ;
block = "{", { statement }, "}" ;
Both productions share the "fn", "(", params, ")", block core; the only difference is the presence of a name.
Detailed Design
Parser changes
- Remove the
fn_literalrule that starts with{and uses->. - When the parser encounters
fnfollowed by(, it produces an anonymous function node. When followed by an identifier and then(, it produces a named function declaration as today. {in expression position no longer needs to disambiguate between closures and dict literals. It is always a dict (or a block in statement position).
AST impact
The existing AST node for function literals already stores a parameter list and a body. The only structural change is that the source representation changes while the AST shape remains the same.
Named functions continue to carry an additional name field.
Interaction with ZE-012 (Type and Returns Sugar)
ZE-012 — Type and Returns Sugar introduces : Type parameter attributes and -> ReturnType return attributes as sugar for @Type and @Returns. In case both proposals are accepted, the full signature syntax becomes:
// Named function with type sugar
fn greet(name: String) -> String {
"Hello, " + name
}
// Anonymous function (closure) with type sugar
const greet = fn(name: String) -> String {
"Hello, " + name
}
The desugaring rules of ZE-012 apply identically to both named and anonymous functions and no special-casing is needed, because they now share the same syntactic structure.
Migration
Existing code using the old closure syntax must be rewritten:
// Before
const add = { a, b -> a + b }
items.map({ x -> x + 1 })
// After
const add = fn(a, b) { a + b }
items.map(fn(x) { x + 1 })
Changes to the Standard Library
The standard library (prelude/, future/) will need its closure usages updated to the new syntax. No new types, modules or functions are introduced.
Alternatives Considered
- Keep both syntaxes: Allow
{ a -> ... }alongsidefn(a) { ... }. Rejected because it preserves the ambiguity and cognitive overhead this proposal aims to eliminate. - Status quo: The brace-arrow form is already implemented but conflicts with the dict literal syntax and diverges from named function declarations.
Acknowledgements
- The previous syntax for closures was inspired by Swift’s
{ arg in }and JavaScript’s(arg) => {}closure syntaxes. - Now it’s inspired by Go (
func(a int) { ... }).