Unified Function and Closure Syntax
This proposal has been accepted and implemented.
You can use this feature in the latest version of Zirric.
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
fn
keyword. - 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) { ... }).