Functions
What Is a Function?
A function is like a recipe card. You write down the steps once, give it a name, and then use that recipe whenever you need it -- without rewriting the whole thing.
Here greet is a recipe that prints "Hello!". We call (use) it twice, so
"Hello!" appears twice.
Defining a Function: fn
To create a function, use the fn keyword:
fn-- tells Pebble "I'm writing a new recipe"- name -- the label on the recipe card (e.g.
greet,add,square) - parameters -- ingredient slots (more on these below)
- body -- the instructions inside
{ }
Parameters: The Ingredient Slots
Parameters are like blank spaces on a recipe card where you write in the specific ingredients each time you use it:
a and b are parameters -- placeholders that get filled in when you
call the function.
Calling a Function
To use a function, write its name followed by parentheses with the values (called arguments) you want to pass in:
Here 3 and 4 are arguments -- the actual ingredients you hand to the
recipe. They fill the parameter slots: a gets 3, b gets 4.
You can call functions with zero, one, or many arguments:
Arguments can be any expression, not just simple values:
That second line shows nested calls -- a function call inside another function call, like a recipe that says "first make the sauce (another recipe), then add it to the dish."
Default Parameters
Sometimes a recipe has a go-to ingredient that you use most of the time. Instead of writing it out every single call, you can put a default value right on the recipe card. Think of it as pre-filling an ingredient slot with your usual choice -- you can still swap it out, but if you don't, the default kicks in.
fn greet(name, greeting = "Hello") {
print("{greeting} {name}")
}
greet("Alice") # prints: Hello Alice
greet("Alice", "Hi") # prints: Hi Alice
greeting has a default of "Hello". When you call greet("Alice"), you
only pass one argument, so greeting uses its default. When you call
greet("Alice", "Hi"), the "Hi" you provided overrides the default.
You can have several defaults:
fn make_point(x, y = 0, z = 0) {
return [x, y, z]
}
print(make_point(5)) # prints: [5, 0, 0]
print(make_point(5, 10)) # prints: [5, 10, 0]
print(make_point(5, 10, 15)) # prints: [5, 10, 15]
Rules to remember:
- Required parameters must come before any with defaults. You can't write
fn f(a = 1, b)-- Pebble won't know which slotbshould fill. - Defaults must be literal values (numbers, strings, booleans, or
null). You can't use a variable or an expression like1 + 2as a default.
Giving Back a Result: return
The return keyword is like handing back the finished dish. It sends a value
out of the function:
A function can also return without a value (a bare return), which gives
back null -- meaning "nothing here, I'm done":
Returning Multiple Values
Sometimes a function needs to hand back more than one result -- like a recipe that produces both a main dish and a side dish. Pebble lets you return multiple values by separating them with commas:
Under the hood, return b, a creates a list [b, a] and returns it.
On the other side, let x, y = ... unpacks the list so each value
lands in its own variable.
See Tuple Unpacking for the full story --
including const unpacking, reassignment unpacking, and the swap
idiom.
Functions Are Values
In Pebble, functions are first-class values -- just like numbers and strings. That means you can store a function in a variable, put it in a list, or pass it as an argument to another function:
fn double(x) { return x * 2 }
let f = double
print(f(5)) # prints: 10
print(type(double)) # prints: fn
Think of it this way: the recipe card itself is a thing you can pick up, hand to someone, or put in a drawer. You can use it later, or give it to a different chef.
Anonymous Functions
Sometimes you need a tiny recipe that you'll only use once. Instead of giving it a name, you can write it inline:
This creates a function without the usual fn name(...) form -- just
fn(params) { body } directly in an expression. It's especially useful with
higher-order functions like map, filter, and reduce.
Putting It All Together
Here's a program that uses functions with other features:
fn greet(name) {
print(name)
}
fn count_up(n) {
for i in range(n) {
print(i)
}
}
greet("Alice")
count_up(3)
Functions can contain loops, conditions, and other statements -- you can nest them however you like to build up complex behaviour from simple pieces.
What Can Go Wrong?
The parser catches several mistakes:
| Code | Error |
|---|---|
foo(1, 2 |
Expected ')' after arguments |
foo(1,) |
Unexpected token ')' |
fn () { } |
Expected function name after 'fn' |
fn greet { } |
Expected '(' after function name |
fn f(a = 1, b) { } |
Required parameter cannot follow a parameter with a default |
fn f(a = 1 + 2) { } |
Default parameter values must be literals |
Async Functions
Regular functions run immediately when you call them. Sometimes you need functions that can pause and resume -- for example, waiting for I/O or running multiple tasks at the same time. That's what async functions are for:
When you call an async function, it doesn't run right away. Instead, it returns a coroutine that the event loop will execute later. See Async / Await for the full story.
Summary
| Concept | Analogy |
|---|---|
| Function | A recipe card |
| Parameter | An ingredient slot on the recipe |
| Default parameter | A pre-filled ingredient slot |
| Argument | The actual ingredient you hand over |
return |
Handing back the finished dish |
Function call () |
Using a recipe |
| Async function | A recipe the kitchen runs in the background |