Functiones
Functions in Faber are declared using the functio keyword, derived from the Latin functio meaning "performance, execution." This chapter covers function declarations, parameters, return types, async patterns, generators, and lambda expressions.
Declaring Functions
Basic Syntax
A function declaration begins with functio followed by the function name, parameter list in parentheses, optional return type, and the function body in braces:
functio saluta() {
scribe "Salve, Mundus!"
}
Functions that return values specify the return type after an arrow (->) and use redde (Latin "give back, return") to yield the result:
functio nomen() -> textus {
redde "Marcus Aurelius"
}
Parameters
Faber uses type-first syntax for parameters, placing the type before the parameter name. This mirrors natural language order ("a string called name") and aligns with languages like Go, Rust, and Zig:
functio quadratum(numerus n) -> numerus {
redde n * n
}
functio adde(numerus a, numerus b) -> numerus {
redde a + b
}
functio describe(textus nomen, numerus aetas) -> textus {
redde scriptum("§ habet § annos", nomen, aetas)
}
When a parameter has no explicit type annotation, the compiler infers it from usage:
functio duplica(n) -> numerus {
redde n * 2
}
Dual Parameter Naming
Following Swift's pattern, parameters can have separate external (callsite) and internal (body) names using ut (Latin "as"):
functio greet(textus location ut loc) {
scribe loc # internal name
}
greet(location: "Roma") # external name at callsite
The ut keyword provides a unified aliasing syntax across the language:
- Imports:
ex norma importa scribe ut s - Destructuring:
ex persona fixum nomen ut n - Parameters:
textus location ut loc
All three express the same concept: "X, known locally as Y."
Optional Parameters
The si modifier (Latin "if") marks a parameter as optional. Without a default value, the parameter type becomes nullable (ignotum<T>):
functio greet(textus nomen, si textus titulus) -> textus {
si titulus est nihil {
redde scriptum("Salve, §!", nomen)
}
redde scriptum("Salve, § §!", titulus, nomen)
}
greet("Marcus") # titulus receives nihil
greet("Marcus", "Dominus") # titulus receives "Dominus"
Default Values
Default values use vel (Latin "or"), consistent with the nullish coalescing operator in expressions:
functio paginate(si numerus pagina vel 1, si numerus per_pagina vel 10) -> textus {
redde scriptum("Page § with § items", pagina, per_pagina)
}
paginate() # "Page 1 with 10 items"
paginate(2) # "Page 2 with 10 items"
paginate(2, 25) # "Page 2 with 25 items"
The choice of vel provides consistency: vel already means "or if nil" in expressions like value vel "default", making parameter defaults read naturally as "numerus pagina or 1."
Default values only make sense for owned parameters. Borrowed (de) and mutable (in) parameters require the caller to provide a value since there is nothing to borrow by default.
Rest Parameters
The ceteri modifier (Latin "the rest, the others") collects remaining arguments into an array:
functio sum(ceteri numerus[] nums) -> numerus {
varia total = 0
ex nums pro n {
total += n
}
redde total
}
sum(1, 2, 3, 4, 5) # 15
Rest parameters must come last in the parameter list.
Return Types
Arrow Syntax
The arrow -> specifies a function's return type directly. This is the simplest form and compiles with minimal overhead:
functio compute() -> numerus {
redde 42
}
When the function returns nothing, omit the return type entirely or specify vacuum:
functio doNothing() {
# no return value
}
functio doNothingExplicit() -> vacuum {
redde
}
Latin Verb Forms
Faber offers an alternative syntax using conjugated forms of the Latin verb fieri ("to become"). These verb forms encode additional semantic information about how the function returns values:
| Verb | Tense/Number | Meaning | Semantics |
|---|---|---|---|
fit |
present, sing. | "it becomes" | sync, single value |
fiet |
future, sing. | "it will become" | async, single value |
fiunt |
present, plur. | "they become" | sync, yields multiple |
fient |
future, plur. | "they will become" | async, yields multiple |
The verb forms participate in the Responsum stream protocol, providing structured error handling. Arrow syntax (->) bypasses this protocol for direct returns with zero overhead.
# These are equivalent for simple cases:
functio getId() -> textus { redde "abc" }
functio getId() fit textus { redde "abc" }
The verb syntax becomes valuable when you want stream-based error handling or generator behavior without explicit modifiers.
Async Functions
The futura Modifier
The futura modifier (Latin "future things," neuter plural of futurus) marks a function as asynchronous. Combined with arrow syntax, it returns a Promise:
futura functio fetchData(textus url) -> textus {
fixum response = cede fetch(url)
redde response.text()
}
The choice of futura leverages Latin's grammatical future tense to express temporal semantics: the result will be available in the future.
The cede Keyword
Inside async functions, cede (Latin "yield, give way, surrender") awaits a promise:
futura functio processAll(textus[] urls) -> textus[] {
varia results = []
ex urls pro url {
fixum data = cede fetchData(url)
results.adde(data)
}
redde results
}
The etymology captures the semantics precisely: the function cedes control until the async operation completes.
Async via Verb Form
The fiet verb ("it will become") implies async behavior without the futura modifier:
functio fetchData() fiet textus {
redde "data"
}
This is equivalent to futura functio fetchData() -> textus but participates in the Responsum protocol.
Gerundive Declarations
The gerundive forms figendum and variandum provide implicit await:
figendum data = fetchData(url) # immutable, implicit await
variandum result = fetchInitial() # mutable, implicit await
These are equivalent to fixum x = cede y() and varia x = cede y(). The gerundive signals futurity: the value will be fixed/varied once the operation completes. If the right-hand side is synchronous, it passes through unchanged.
Generator Functions
The cursor Modifier
The cursor modifier (Latin "runner," from currere "to run") creates a generator function:
cursor functio range(numerus n) -> numerus {
ex 0..n pro i {
cede i
}
}
In generator context, cede yields values rather than awaiting them, reusing the same keyword for both semantics based on function context.
Generator via Verb Forms
The fiunt verb ("they become," plural) implies generator behavior:
functio range(numerus n) fiunt numerus {
ex 0..n pro i {
cede i
}
}
For async generators that yield promises, use fient ("they will become"):
functio fetchAll(textus[] urls) fient textus {
ex urls pro url {
cede fetch(url)
}
}
Iterating Over Generators
Generator results can be consumed with ex...pro loops:
ex rangeSync(5) pro num {
scribe num
}
Generic Functions
Type Parameters with prae
The prae keyword (Latin "before") declares compile-time type parameters. Combined with typus ("type"), it introduces generic type variables:
functio max(prae typus T, T a, T b) -> T {
si a > b { redde a }
redde b
}
fixum larger = max(10, 20) # T inferred as numerus
fixum longer = max("alpha", "beta") # T inferred as textus
Type parameters must come first in the parameter list, followed by regular parameters. This matches conventions in TypeScript, Rust, and Zig.
Multiple type parameters are supported:
functio pair(prae typus T, prae typus U, T first, U second) -> [T, U] {
redde [first, second]
}
Lambda Expressions
Basic Syntax
Lambda expressions use pro (Latin "for, on behalf of") followed by parameters, a colon, and an expression:
fixum double = pro x: x * 2
fixum add = pro a, b: a + b
The colon separates parameters from the body. For single expressions, the result is implicitly returned.
With Return Type Annotation
When type annotation is needed, use an arrow before the colon:
fixum add = pro a, b -> numerus: a + b
fixum isPositive = pro n -> bivalens: n > 0
Block Bodies
For multi-statement lambdas, use braces and explicit redde:
fixum process = pro x {
varia result = x * 2
result += 10
redde result
}
Zero-Parameter Lambdas
When a lambda takes no parameters, place the colon immediately after pro:
fixum getFortyTwo = pro: 42
Async Lambdas
The fiet keyword creates async lambdas:
fixum fetchAndProcess = fiet url {
fixum data = cede fetch(url)
redde process(data)
}
This is useful for callbacks in async contexts:
app.post("/users", fiet context {
redde context.json()
})
Common Patterns
Lambdas shine in functional operations:
fixum numbers = [1, 2, 3, 4, 5]
# Filter
fixum evens = numbers.filter(pro x: x % 2 == 0)
# Map
fixum doubled = numbers.map(pro x: x * 2)
# Reduce
fixum sum = numbers.reduce(0, pro acc, x: acc + x)
Ownership Prepositions in Parameters
Latin prepositions indicate how parameters are passed and what the function may do with them:
de(from/concerning): borrowed, read-only accessin(into): mutable borrow, the function may modify the value
functio processPoints(de Point[] points, in Point[] targets) {
# points is borrowed (read-only)
# targets is mutably borrowed
ex points pro point {
targets.adde(point)
}
}
These prepositions combine naturally with other parameter modifiers:
functio analyze(textus source, de si numerus depth) -> numerus {
si depth est nihil { redde 3 }
redde depth
}
The prepositions express semantic intent about ownership and mutability. They serve as documentation for readers and enable stricter checking in future compiler versions.
Summary
Faber's function system balances Latin linguistic authenticity with practical programming needs:
functiofor declaration,reddefor return- Type-first parameters with
utaliasing sifor optional,velfor defaults,ceterifor rest- Arrow
->for direct returns, verb forms for stream protocol futuraandcursormodifiers, orfiet/fiunt/fientverbscedefor await (async) or yield (generator)prae typusfor genericsprofor lambdas with optionalfietfor async
The Latin vocabulary maps naturally to programming concepts: futura captures async's temporal nature, cede captures yielding control, and verb conjugations encode sync/async and single/multiple semantics grammatically.