Functions
Functions are the core building block in Almide. Every function has explicit parameter types and a return type. The body is a single expression after =.
Basic syntax
Section titled “Basic syntax”fn add(a: Int, b: Int) -> Int = a + b
fn greet(name: String) -> String = "Hello, ${name}!"Key rules:
- Parameter types are required
- Return type is required
- The body follows
=(not{}) - The last expression is the return value (no
returnkeyword)
Multi-statement bodies
Section titled “Multi-statement bodies”Use a block { } for multiple statements. The last expression in the block is the return value:
fn classify(n: Int) -> String = { let abs_n = int.abs(n) let label = if abs_n > 100 then "large" else "small" "${label}: ${int.to_string(n)}"}effect fn
Section titled “effect fn”Functions with side effects (I/O, network, randomness) must be marked effect fn:
effect fn save(path: String, content: String) -> Result[Unit, String] = { fs.write(path, content)! ok(())}The effect modifier enforces a strict boundary:
- Calling an
effect fnfrom a non-effect function is a compile error effect fntypically returnsResult[T, E]- Use the
!operator to unwrapResultvalues and propagate errors explicitly
fn pure() -> String = fs.read_text("file.txt") // Compile error: cannot call effect fn
effect fn safe() -> Result[String, String] = fs.read_text("file.txt") // OK: effect fn calling effect fnSee Error Handling for the !, ??, and ? operators.
Parameters
Section titled “Parameters”Required parameters
Section titled “Required parameters”All parameters require type annotations:
fn distance(x1: Float, y1: Float, x2: Float, y2: Float) -> Float = float.sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2)Default arguments
Section titled “Default arguments”Parameters can have default values. All parameters after the first default must also have defaults:
fn connect(host: String, port: Int = 8080, secure: Bool = false) -> String = "${host}:${int.to_string(port)}"Named arguments
Section titled “Named arguments”Arguments can be named at the call site, useful with default parameters:
connect("localhost")connect("localhost", port: 443, secure: true)connect(host: "localhost", secure: true) // skip port, use defaultNamed arguments after positional ones are allowed. Positional arguments after named ones are not:
connect("localhost", secure: true) // OKconnect(secure: true, "localhost") // Compile errorPredicate functions
Section titled “Predicate functions”Functions ending in ? must return Bool:
fn empty?(xs: List[Int]) -> Bool = list.len(xs) == 0fn positive?(n: Int) -> Bool = n > 0The ? suffix is a naming convention enforced by the compiler. It cannot be used with non-Bool return types.
Lambdas
Section titled “Lambdas”Lambdas (anonymous functions) use the (params) => expr syntax:
let double = (x: Int) => x * 2Type annotations on lambda parameters are optional when inferrable:
let xs = [1, 2, 3]let doubled = list.map(xs, (x) => x * 2)let evens = list.filter(xs, (x) => x % 2 == 0)Multi-parameter lambdas:
let add = (a: Int, b: Int) => a + blist.fold([1, 2, 3], 0, (acc, x) => acc + x) // 6Use _ for unused lambda parameters:
list.map(xs, (_) => 0) // replace every element with 0There is only one lambda syntax. No shorthand forms like {x -> x} or x => x.
Visibility
Section titled “Visibility”Three visibility levels control access to functions:
| Modifier | Scope | Rust equivalent |
|---|---|---|
| (none) | Public (default) | pub |
mod | Same project only | pub(crate) |
local | Same file only | (private) |
fn public_api() -> String = "anyone can call this"mod fn internal_helper() -> String = "project-internal"local fn file_private() -> String = "only this file"Visibility applies to fn, type, and let declarations:
local type Internal = { data: String }mod let THRESHOLD = 100Modifier order
Section titled “Modifier order”When combining modifiers, the order is: [local|mod]? effect? fn
mod effect fn internal_io() -> Result[Unit, String] = { println("internal") ok(())}
local effect fn private_io() -> Result[Unit, String] = { println("private") ok(())}Holes and todo
Section titled “Holes and todo”Use _ (hole) or todo(msg) as placeholder implementations:
fn parse(text: String) -> Ast = _ // type-checked stubfn optimize(ast: Ast) -> Ast = todo("implement later") // todo with messageThe compiler accepts any expected type for holes and reports the expected type, available variables, and suggestions.
UFCS (Uniform Function Call Syntax)
Section titled “UFCS (Uniform Function Call Syntax)”f(x, y) and x.f(y) are equivalent. The compiler resolves automatically:
string.trim(text) // canonical module.function formtext.trim() // UFCS dot-call form (equivalent)
string.split(text, ",")text.split(",") // equivalentPipe operator
Section titled “Pipe operator”The pipe operator |> passes a value as the first argument to the next function:
text |> string.trim |> string.split(",")// equivalent to: string.split(string.trim(text), ",")Use _ as a placeholder when the piped value is not the first argument:
xs |> list.filter(_, (x) => x > 0)Pipe into match:
list.get(args, 1) |> match { some(cmd) => cmd, none => "default",}Next steps
Section titled “Next steps”- Control Flow — if, for, while, guard
- Error Handling — Result, Option, unwrap operators, effect fn
- Modules & Imports — organizing code across files