Pre-alpha: Published for early evaluation only

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:

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:

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:

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.