Lambda Functions

July 7, 2026 · View on GitHub

This document covers Lambda's function system, including pure functional (fn) and procedural (pn) functions, parameters, closures, and higher-order functions.

Related Documentation:


Table of Contents

  1. Function Overview
  2. Function Declarations
  3. Function Parameters
  4. Function Calls
  5. Method-Style Calls
  6. Closures
  7. Higher-Order Functions
  8. Procedural Functions

Function Overview

Lambda supports two kinds of functions:

KindKeywordCharacteristics
FunctionalfnPure, immutable, expression-based
ProceduralpnMutable state, side effects, imperative

Quick Comparison

// Pure function — no side effects
fn double(x: int) => x * 2

// Procedural function — can have side effects
pn save_result(data) {
    output(data, "./temp/output.json")
}

Function Declarations

Function of Statements

// Statements as function body

fn add(a: int, b: int) int {
    a + b
}

fn greet(name: string) string {
    "Hello, "
    name
    "!"
}

fn process(data) {
    let filtered = data where ~ > 0;
    let doubled = filtered | ~ * 2;
    doubled
}

Function of Expression

// Single expression body
fn multiply(x: int, y: int) => x * y

fn square(n: int) => n ** 2

Anonymous Functions

// Lambda expressions
(x: int) => x * 2

fn (x: int, y: int) { ... }

// With inferred types
(x) => x * 2

Function Parameters

Required Parameters

Parameters with type annotations are required:

fn greet(name: string) => "Hello, " ++ name
fn add(a: int, b: int) => a + b

add(5, 3)       // 8
add(5)          // Error: missing required parameter

Optional Parameters

Use ? before the type to make a parameter optional:

fn greet(name: string, title?: string) => {
    if (title) title ++ " " ++ name
    else name
}

greet("Alice")           // "Alice"
greet("Alice", "Dr.")    // "Dr. Alice"

Note: a?: T means the parameter is optional (may be null). This is different from a: T? where the type is nullable but the parameter is required.

Default Parameter Values

Parameters can have default values:

fn greet(name = "World") => "Hello, " ++ name ++ "!"
fn power(base: int, exp: int = 2) => base ** exp

greet()              // "Hello, World!"
greet("Lambda")      // "Hello, Lambda!"
power(3)             // 9 (3**2)
power(2, 10)         // 1024 (2**10)

Default expressions can reference earlier parameters:

fn make_rect(width: int, height = width) => {
    width: width, height: height
}

make_rect(10)        // {width: 10, height: 10}
make_rect(10, 20)    // {width: 10, height: 20}

Named Arguments

Arguments can be passed by name in any order:

fn create_user(name: string, age: int, active: bool = true) => {
    name: name, age: age, active: active
}

// All equivalent:
create_user("Alice", 30, true)
create_user(name: "Alice", age: 30, active: true)
create_user(age: 30, name: "Alice")  // Order independent

// Skip optional parameters
create_user("Bob", 25)               // active defaults to true

Rules:

  • Positional arguments must come before named arguments
  • Named arguments can appear in any order
  • Cannot provide the same argument both positionally and by name

Variadic Parameters

Use ... to accept any number of additional arguments:

fn sum_all(...) => sum(varg())
fn printf(fmt: string, ...) => format(fmt, varg())

sum_all(1, 2, 3, 4, 5)       // 15
sum_all()                     // 0
printf("%s is %d", "x", 42)  // "x is 42"

Access variadic arguments with varg():

CallReturns
varg()List of all variadic arguments
varg(n)The nth variadic argument (0-indexed)
fn first_or_default(default, ...) => {
    if (len(varg()) > 0) varg(0)
    else default
}

first_or_default(0, 1, 2, 3)   // 1
first_or_default(0)             // 0

Parameter Order

Parameters must be declared in this order:

required → optional (?) → defaults → variadic (...)
// Valid
fn valid(req: int, opt?: int, def: int = 10, ...) => ...

// Invalid
fn invalid(opt?: int, req: int) => ...  // Error

Parameter Mismatch Handling

SituationBehavior
Missing required argumentCompile-time error
Missing optional argumentFilled with null
Missing default argumentEvaluates default expression
Extra arguments (no variadic)Warning, discarded

Function Calls

Basic Calls

add(5, 3)
greet("Alice")
calculate(2.5, 3.0, "add")

Chained Calls

// Direct chaining
process(filter(sort(data)))

// Method-style chaining (preferred)
data.sort().filter(x => x > 0).process()

Partial Application

// Create specialized functions
fn add(a: int, b: int) => a + b

let add5 = (x) => add(5, x)
add5(3)   // 8

Method-Style Calls

System functions can be called using method syntax:

// Traditional prefix style
len(arr)
sum([1, 2, 3])
slice("hello", 0, 3)

// Method style (equivalent)
arr.len()
[1, 2, 3].sum()
"hello".slice(0, 3)

// Array operations
[3, 1, 4, 1, 5].sort()          // [1, 1, 3, 4, 5]
[3, 1, 4, 1, 5].sum()           // 14
[3, 1, 4, 1, 5].unique()        // [3, 1, 4, 5]

// Chained
[5, 3, 1, 4, 2].sort().reverse()  // [5, 4, 3, 2, 1]

// Type conversion
42.string()                      // "42"
"123".int()                      // 123
3.14.floor()                     // 3

Supported Functions

CategoryPrefix StyleMethod Style
Typelen(arr)arr.len()
Typetype(val)val.type()
Typestring(42)42.string()
Stringslice(s, 0, 5)s.slice(0, 5)
Stringcontains(s, "x")s.contains("x")
Collectionreverse(arr)arr.reverse()
Collectionsort(arr)arr.sort()
Collectiontake(arr, 3)arr.take(3)
Statssum(nums)nums.sum()
Statsavg(nums)nums.avg()
Mathabs(x)x.abs()
Mathmath.sqrt(x)x |> math.sqrt

Method Chaining

Method syntax enables fluent operations:

// Chained method calls
let result = data
    .filter(x => x > 0)
    .map(x => x * 2)
    .sort()
    .take(10)
    .sum()

// Equivalent nested calls (harder to read)
let result = sum(take(sort(map(filter(data, x => x > 0), x => x * 2)), 10))

Closures

Closures are functions that capture variables from their enclosing scope.

Basic Closure

let multiplier = 3
let triple = (x) => x * multiplier
triple(5)  // 15

Capturing Multiple Variables

fn make_linear(slope: int, intercept: int) {
    fn eval(x) => slope * x + intercept
    eval
}

let line = make_linear(2, 10)
line(5)    // 20 (2*5 + 10)
line(10)   // 30 (2*10 + 10)

Nested Closures

fn level1(a) {
    fn level2(b) {
        fn level3(c) {
            fn level4(d) => a + b + c + d
            level4
        }
        level3
    }
    level2
}

level1(1)(2)(3)(4)   // 10

Closures with Let Bindings

fn make_counter(start: int) {
    let initial = start * 2
    fn count(step) => initial + step
    count
}

let counter = make_counter(5)
counter(3)   // 13 (5*2 + 3)

Closure Captures Are Immutable Snapshots

Closures capture values by snapshot. A nested fn or pn may read a captured binding, but it cannot assign to the captured binding or mutate through it with an interior write such as xs[0] = value. Mutable state that must be updated by a helper should be passed explicitly as a var parameter, or returned as a new value.

pn main() {
    var base = 40
    pn add_two() {
        base + 2       // read-only capture
    }
    let f = add_two
    print(f())         // 42
}
pn bump_first(var xs: any[]) {
    xs[0] = 42
}

pn main() {
    var xs: any[] = [1, 2, 3]
    bump_first(xs)
    print(xs[0])       // 42
}

Capture and mutability semantics:

  • Captures are by value: each closure gets a snapshot at creation time.
  • Captured bindings are read-only, including interior writes through arrays, maps, elements, and objects.
  • var parameters are explicit inout parameters; callers must pass a mutable var binding.
  • A var parameter is invariant: the argument type must match the parameter type exactly. For covariant array assignment, declare the local binding with the wider type, such as var xs: any[] = [1, 2, 3].
  • Named closures must be called through a variable (let f = name; f()) to pass the environment.

Higher-Order Functions

Functions that take or return functions.

Functions as Arguments

fn apply(f, x) => f(x)
fn map_array(arr, f) => (for (x in arr) f(x))

apply((x) => x * 2, 5)           // 10
map_array([1, 2, 3], (x) => x ** 2)  // [1, 4, 9]

Functions as Return Values

fn compose(f, g) => (x) => g(f(x))

let add1 = (x) => x + 1
let double = (x) => x * 2

let add1_then_double = compose(add1, double)
add1_then_double(5)   // 12 (double(add1(5)))

Common Higher-Order Patterns

// Filter
fn filter(arr, pred) => arr where pred(~)

// Map
fn map(arr, f) => arr | f(~)

// Reduce/Fold
fn reduce(arr, init, f) => {
    let result = init
    for (x in arr) {
        result = f(result, x)
    }
    result
}

// Usage
filter([1, 2, 3, 4, 5], (x) => x > 2)      // [3, 4, 5]
map([1, 2, 3], (x) => x * 2)               // [2, 4, 6]
reduce([1, 2, 3, 4], 0, (a, b) => a + b)   // 10

Procedural Functions

Procedural functions (pn) allow mutable state, side effects, and imperative control flow. See Lambda Procedural Programming for full documentation.


System Functions Reference

Lambda provides extensive built-in functions. See Lambda_Sys_Func.md for complete documentation.

Function Categories

CategoryExamplesDescription
Typetype, len, int, float, stringType conversion and inspection
Mathabs, round, floor, ceil, sqrt, logMathematical operations
Statisticalsum, avg, median, varianceStatistical analysis
Collectionslice, reverse, sort, uniqueCollection manipulation
Stringupper, lower, trim, split, joinString operations
I/Oinput, format, print, output, existsInput/output
Date/Timedatetime, date, time, today, nowDate and time

This document covers Lambda's function system. For built-in function details, see Lambda_Sys_Func.md. For expressions, see Lambda Expressions. For procedural programming, see Lambda Procedural.