5. Functions¶
Functions are user-defined procedures that encapsulate reusable code. They are the primary abstraction mechanism in the language.
5.1 What are Functions¶
Functions differ from operators:
- Operators implement trait methods and are the fundamental building blocks
- Functions are user-defined procedures that use operators
- Operators and functions cannot share names
Related: See Section 1 "Operators vs Functions" for a complete explanation of the distinction.
5.2 Defining Functions¶
Syntax: (inputs -- outputs) { body } ::name fn
The function signature specifies stack effects (what is consumed and produced), the body contains the implementation, and the name identifies the function.
Examples:
// Simple function - requires Multiplyable trait
(Multiplyable -- Multiplyable) { dup * } ::square fn
// Use it
5 square // => 25
// Multiple inputs and outputs
(Number Number -- Number Number) {
over over / swap %
} ::divmod fn
10 3 divmod // => 3 1 (quotient remainder)
Function Bodies: The { } braces contain a TokenString that is parsed as the function body when the function is defined. See Section 5.5 for details on TokenStrings.
Related: See Section 10.2 for generic functions with type parameters.
5.3 Calling Functions¶
Functions are called by simply writing their name. The postfix notation means arguments must be on the stack before the function name:
// Define a function
(Number Number -- Number) { + } ::add fn
// Call it
3 4 add // => 7
// Chain functions
5 square 2 * // => 50 (square 5, then multiply by 2)
Function Chaining: Since everything is postfix, functions naturally chain left-to-right:
10 square 2 / 5 + // ((10²) / 2) + 5 = 55
5.4 Recursion¶
Functions can call themselves recursively:
(Number -- Number) {
dup 1 <=
{ drop 1 }
{ dup 1 - factorial * }
if
} ::factorial fn
5 factorial // => 120
Stack Considerations: Recursive functions consume stack space. Deep recursion may cause stack overflow. Consider iterative alternatives for performance-critical code.
5.5 Token Strings¶
Token strings are lexed but unparsed code blocks enclosed in { }. They are parsed differently depending on which operator uses them.
Operators that parse TokenStrings:
fn- Parses as function body (code block)trait- Parses as trait definition (method signatures)impl- Parses as trait implementation (method definitions)eval- Parses and executes as code block immediatelylambda- Parses as code block, pushes the block as a callable valueif- Parses both TokenStrings as code blocks (then/else branches)while- Parses both TokenStrings as code blocks (condition/body)for- Parses TokenString as code block (loop body)match- Parses TokenString as pattern matching armsmap,filter,reduce,each- Parse TokenStrings as code blocks for array operations
Examples:
// Function body (parsed by fn)
(Number -- Number) { dup * } ::square fn
// Conditional branches (parsed by if)
x 0 > { "positive" print } { "negative" print } if
// Loop body (parsed by while)
{ dup 10 < } { dup print 1 + } while
5.6 Lambda Functions¶
The lambda operator converts a TokenString into a callable code block that can be stored and passed around:
{ dup * } lambda // Creates a callable code block
::square_fn swap // Store reference to the lambda
// Later use:
5 square_fn eval // Calls the lambda: pushes 25
Use Cases:
- Creating first-class functions
- Passing code blocks as values
- Dynamic dispatch
- Higher-order operations
Example with arrays:
// Store a lambda and use it with map
{ 2 * } lambda ::double swap
[1 2 3 4] double map // => [2 4 6 8]
Related: See Section 11.1 for the
evaloperator used to execute lambdas.