Defining Functions (fn name(x: int) -> str)
Functions in Luma let you group logic into reusable, well-typed building blocks. They follow the same principles as the rest of the language:
- Clear and explicit: parameter and return types are always visible.
- Minimal but useful: one main way to define functions.
- Consistent with lambdas: function types use the same
(...) -> ...shape.
This page covers:
- Block-style functions with
fnandreturn - Single-expression shortcut using
=> - Parameters and return types
- Function types for higher-order functions
- Optionals in function signatures
- Common patterns and gotchas
Basic Function Syntax
The simplest function has:
- A
name - Zero or more parameters with types
- A return type
- A body in
{ ... }with areturnstatement
fn greet(name: str) -> str {
return "Hello, ${name}!"
}fn- starts a function definitiongreet- function namename: str- parameter name of typestr-> str- function returns astr- Body - everything inside
{ ... }, must eventuallyreturnastr
Calling the function:
message: str = greet("Luma")
print(message) // Hello, Luma!Single-Expression Functions (=>)
For small functions, you can skip the braces and return keyword.
fn add(a: int, b: int) -> int => a + bThis is equivalent to:
fn add(a: int, b: int) -> int {
return a + b
}Another example:
fn greet(name: str) -> str => "Hello, ${name}!"Rules for => functions:
- The function body is a single expression after
=>. - The expression must match the declared return type.
- You still write the parameter list and
-> ReturnType.
Function Types and Higher-Order Functions
Luma supports lambdas (x -> x > 2).
Function types reuse the same arrow syntax: (ParamTypes...) -> ReturnType.
Function type syntax
// A function that takes an int and returns an int
doubler: (int) -> int
// A function that takes a str and returns a str
formatter: (str) -> strYou can use these types in:
- Variable declarations
- Parameters
- Return types
Passing a function as a parameter
fn apply_twice(f: (int) -> int, x: int) -> int {
return f(f(x))
}
fn increment(n: int) -> int => n + 1
result: int = apply_twice(increment, 5)
print(result) // 7You can also pass lambdas directly:
result2: int = apply_twice(n -> n * 2, 3)
print(result2) // 12Returning a function
fn make_prefixer(prefix: str) -> (str) -> str {
return name -> "${prefix}${name}"
}
add_hello: (str) -> str = make_prefixer("Hello, ")
print(add_hello("Luma")) // Hello, LumaThis pattern is useful for building reusable, configured helpers.
Optionals in Function Signatures
Functions can take and return optional types, just like variables.
Optional parameters
fn greet_optional(name: ?str) -> str {
if name == nil {
return "Hello, stranger!"
}
return "Hello, ${name}"
}Usage:
n1: ?str = nil
n2: ?str = "Luma"
print(greet_optional(n1)) // Hello, stranger!
print(greet_optional(n2)) // Hello, LumaOptional return types
Optional returns are great for “maybe found” operations:
fn find_index(nums: [int], target: int) -> ?int {
nums.walk(i, v) -> {
if v == target {
return i
}
}
return nil
}
nums: [int] = [1, 2, 3]
idx: ?int = find_index(nums, 2)
if idx != nil {
print("Index: ${idx}")
}Functions, Interpolation, and Collections
Functions work naturally with Luma’s other features:
Interpolation in return values
fn summary(count: int, avg: float) -> str {
return "Count = ${count}, avg = ${avg}"
}Working with lists and maps
fn sum(nums: [int]) -> int {
total: int = 0
nums.walk(x) -> {
total = total + x
}
return total
}
fn keys_of(m: {str: int}) -> [str] {
ks: [str] = []
m.walk(k, v) -> {
ks.add(k)
}
return ks
}Best Practices
A few guidelines to keep functions readable and “Luma-like”:
- Keep them small: one clear responsibility per function.
- Use meaningful names:
sum,filter_active,to_messageinstead off1,doStuff.
- Use
=>only when the implementation is truly a single, simple expression. - Prefer explicit return types rather than inference (for now).
- Use optional return types for “maybe” results instead of magic values like
-1.
Gotchas
A few things to watch out for:
-
Missing return
Every code path in a function must return the declared type.
fn bad(x: int) -> int {
if x > 0 {
return x
}
// ERROR: no return for x <= 0
}-
Return type must match
Returning different types based on branches is not allowed:
fn weird(flag: bool) -> int {
if flag {
return 1
} else {
return "nope" // ERROR: str vs int
}
}-
Function type must match
When passing functions as arguments, parameter and return types must align exactly:
fn apply(f: (int) -> int, x: int) -> int {
return f(x)
}
fn wrong(s: str) -> str => s
apply(wrong, 1) // ERROR: (str) -> str is not (int) -> intQuick Reference
Define a function (block)
fn name(param1: Type1, param2: Type2) -> ReturnType {
// ...
return value
}Define a function (single expression)
fn name(param: Type) -> ReturnType => expressionFunction type in a variable
op: (int) -> int
op = n -> n * 2Higher-order function
fn apply(f: (int) -> int, x: int) -> int {
return f(x)
}Optional returns
fn find(...args) -> ?int {
// ...
return nil // or: return index
}