Functions
Functions in Parsley are first-class values created with fn. They are always anonymous — naming happens through let binding. The body is a block, and the last expression is the implicit return value.
Basic Syntax
let double = fn(x) { x * 2 }
let add = fn(a, b) { a + b }
let hello = fn() { "hello" }
let thunk = fn { 99 } // parens optional when no parameters
double(5) // 10
add(3, 4) // 7
thunk() // 99
Return Values
The last expression in a block is the return value — no return needed:
let double = fn(x) {
x * 2 // automatically returned
}
When to Use return
In most Parsley code, return is redundant. The language is expression-oriented: functions, if/else, and for all produce values naturally. Prefer implicit returns for cleaner code.
Don't reach for return when:
- The function body is a single expression
- You can use
check...elsefor guard clauses - The last expression is already your result
// ❌ Unnecessary return
let double = fn(x) { return x * 2 }
// ✅ Implicit return
let double = fn(x) { x * 2 }
// ❌ Return for guards
let process = fn(x) {
if (!x) { return null }
x.value
}
// ✅ check...else for guards
let process = fn(x) {
check x else null
x.value
}
Do use return for early exit from inside loops — this is its primary purpose:
let contains = fn(arr, value) {
for (item in arr) {
if (item == value) {
return true // exits the function, not just the loop
}
}
false
}
Note that stop exits the loop but continues the function; return exits the function entirely. When you need to bail out of a loop and return from the enclosing function, return is the right tool.
Parameter Destructuring
Function parameters can destructure dictionaries and arrays directly:
// Dictionary destructuring
let greet = fn({name, age}) {
name + " is " + age
}
greet({name: "Alice", age: 30}) // "Alice is 30"
// Array destructuring with rest
let process = fn([first, ...rest]) {
{first: first, rest: rest}
}
process([10, 20, 30]) // {first: 10, rest: [20, 30]}
This is the standard pattern for components — a single dict parameter with named fields:
let Card = fn({title, body}) {
<div class="card">
<h2>title</h2>
<p>body</p>
</div>
}
<Card title="Hello" body="World"/>
When a component tag has children, they arrive as contents:
let Wrap = fn({contents}) {
<div class="wrap">contents</div>
}
<Wrap><p>"inner"</p></Wrap>
Closures
Functions capture their enclosing environment by reference:
let make_counter = fn() {
let count = 0
fn() {
count = count + 1
count
}
}
let c = make_counter()
c() // 1
c() // 2
c() // 3
this Binding
When a function is stored as a dictionary value and called as a method, this is automatically bound to the dictionary:
let user = {
name: "Alice",
greet: fn() { "Hello, " + this.name }
}
user.greet() // "Hello, Alice"
this is only available inside methods called via dot notation. Calling the function directly (not through the dict) won't bind this.
First-Class Usage
Functions are values — pass them to methods, store them in arrays, return them from other functions:
[1, 2, 3].map(fn(x) { x * 10 }) // [10, 20, 30]
[1, 2, 3, 4, 5].filter(fn(x) { x > 3 }) // [4, 5]
[1, 2, 3, 4, 5].reduce(fn(acc, x) { acc + x }, 0) // 15
Immediately Invoked
fn() { 42 }() // 42
fn(x) { x * 2 }(5) // 10
Argument Handling
Parsley does not support default parameter values. Missing arguments leave the parameter unbound (using it will cause an "identifier not found" error). Extra arguments are silently ignored.
let f = fn(a, b) { a }
f(1, 2, 3) // 1 (third arg ignored)
f(1) // 1 (b unbound but unused, so no error)
⚠️ Built-in functions and methods enforce arity strictly and will error on wrong argument counts. User-defined functions do not — they silently accept any number of arguments.
Key Differences from Other Languages
- No
functionkeyword — usefn. - No default parameters — use
??inside the body if you need defaults:let x = arg ?? "default". - No arrow functions —
fn(x) { x * 2 }is the only syntax. - No named function declarations — all functions are anonymous; naming is via
letorexport. - Implicit return — the last expression is the return value.
returnis only needed for early exit. thisis dict-scoped — not class-based. It's bound when calling a function through dot notation on a dictionary.
See Also
- Variables & Binding —
let, destructuring, scope - Control Flow —
if/else,for,check - Operators — spread in destructuring
- Tags — component pattern with
fn({contents}) - Modules —
exportandimport