Skip to main content

Anonymous Functions

Anonymous functions (also called lambda expressions or function literals) create inline function values without declaring a named function. They are essential for callbacks, quantifiers, and higher-order programming.

Basic Syntax

Anonymous functions use the same func keyword as named functions but omit the function name:

func (param1 : Type1, param2 : Type2) : ReturnType {
// body
}

Store anonymous functions in variables to use them:

anon-basic.sr9
// Anonymous function stored in a let binding
persistent actor {
public func applyAnon(n : Nat) : async Nat
ensures result == n + 1;
{
// Anonymous function with a postcondition
let anonInc = func (m : Nat) : Nat
ensures result == m + 1;
{
m + 1
};
anonInc(n)
};
}

The anonymous function is assigned to anonInc and then called with anonInc(n).

Expression Body Form

For simple functions, use the expression body form with =:

func (x : Nat) : Nat = x * 2

This is equivalent to:

func (x : Nat) : Nat { x * 2 }

Expression bodies are common in specifications:

anon-expression-body.sr9
// Anonymous functions with expression body syntax
persistent actor {
public func test() : async () {
ghost {
// Expression body uses = instead of { }
let double = pure func (x : Nat) : Nat = x * 2;
let isZero = pure func (n : Nat) : Bool = n == 0;

assert double(5) == 10;
assert isZero(0);
assert not isZero(1);
};
};
}

Pure Anonymous Functions

Mark anonymous functions with pure when they have no side effects. Pure functions:

  • Cannot read or modify state
  • Cannot call impure functions
  • Cannot use await
  • Are safe to use in specifications
anon-pure.sr9
// Pure anonymous functions for use in specifications
persistent actor {
public func checkArray() : async () {
ghost {
let data : [Nat] = [1, 2, 3, 4, 5];

// Pure anonymous function in a quantifier
assert forall<Nat>(pure func (i : Nat) : Bool =
i < data.size() ==> data[i] > 0);
};
};
}

The pure modifier is required when:

  • Using the function in a forall or exists quantifier
  • Passing the function as an argument to an uncontracted higher-order parameter (in verification mode)
  • Storing the function in an uncontracted higher-order variable

Contracts on Anonymous Functions

Anonymous functions support verification contracts just like named functions:

let anonInc = func (m : Nat) : Nat
requires m < 1000;
ensures result == m + 1;
{
m + 1
};

The requires clause specifies preconditions and ensures specifies postconditions. The result keyword refers to the return value in postconditions.

Closures

Anonymous functions can capture variables from their enclosing scope. This creates a closure:

anon-closure.sr9
// Anonymous functions capturing variables from enclosing scope
persistent actor {
public func addN(n : Nat, m : Nat) : async Nat
ensures result == n + m;
{
// This function captures `n` from the enclosing scope
let addToN = pure func (x : Nat) : Nat {
x + n
};
addToN(m)
};
}

The function addToN captures the parameter n from the enclosing function. When called, it uses both its own parameter x and the captured n.

Closure Semantics

Captured variables are passed as implicit parameters during verification. The verifier:

  • Identifies free variables in the function body
  • Adds them as extra parameters in the generated code
  • Preserves their values at the point of capture

Mutable Function Variables

Store anonymous functions in var bindings to reassign them:

anon-mutable.sr9
// Mutable function variables with pure anonymous functions
persistent actor {
type Operation = (m : Nat) -> Nat contract {
ensures result >= m;
};

public func useCurrent(n : Nat) : async Nat
ensures result == n + 1;
{
// Store anonymous function in a contract-typed mutable variable.
var operation : Operation = pure func (m : Nat) : Nat
ensures result == m * 2;
{
m * 2
};

// Reassign the operation to a different pure anonymous function.
operation := pure func (m : Nat) : Nat
ensures result == m + 1;
{
m + 1
};

operation(n)
};
}

When stored in mutable variables, anonymous functions must be pure unless the variable uses a contract-typed function type. This ensures the verifier can reason about which function is called at each call site.

Higher-Order Function Requirements

In verification mode, function values used as higher-order parameters must be pure or carry type contracts. Uncontracted callback types are treated as pure.

anon-impure-reject_reject.sr9
// ERROR: Higher-order function values must be pure in verification mode
module {
// Impure function - no `pure` modifier
func addOne(x : Nat) : Nat { x + 1 };

// Function taking a function parameter
func apply(f : Nat -> Nat, x : Nat) : Nat {
f(x)
};

public func test(n : Nat) : Nat {
// REJECT: passing impure function to higher-order parameter
// Error M0360: higher-order function values must be pure
apply(addOne, n)
};
}

The error M0360: higher-order function values must be pure occurs when passing an impure function to an uncontracted higher-order parameter. Add the pure modifier or use a contract-typed parameter and a contract-bearing value.

Quantifiers with Anonymous Functions

The forall and exists quantifiers take anonymous functions as their predicate:

// All elements are positive
assert forall<Nat>(pure func (i : Nat) : Bool =
i < arr.size() ==> arr[i] > 0);

// At least one element is zero
assert exists<Nat>(pure func (i : Nat) : Bool =
i < arr.size() and arr[i] == 0);

For explicit trigger control, use forallWith:

anon-quantifier-trigger.sr9
// Anonymous functions with explicit triggers in quantifiers
persistent actor {
public func check() : async () {
ghost {
let data : [Bool] = [true, true, true];

// forallWith uses an explicit trigger function
assert forallWith<Nat>(
// The predicate
pure func (i : Nat) : Bool = i < data.size() ==> data[i] == data[i],
// The trigger - guides SMT instantiation
[pure func (i : Nat) : Bool = data[i]]
);
};
};
}

The second argument to forallWith is an array of trigger functions that guide SMT solver instantiation. See quantifier triggers for the solver-facing rules.

Function Types

Anonymous functions have function types written with ->:

// Function from Nat to Nat
let f : Nat -> Nat = pure func (x : Nat) : Nat = x + 1;

// Function from two Ints to Bool
let compare : (Int, Int) -> Bool = pure func (a : Int, b : Int) : Bool = a < b;

// Function returning unit
let log : Text -> () = func (msg : Text) : () { };

Function types are structural. Any function matching the signature is compatible.

Common Patterns

Callback Pattern

Anonymous functions work as callbacks:

func withDefault<T>(opt : ?T, default : () -> T) : T {
switch (opt) {
case (?v) { v };
case (null) { default() };
}
};

// Usage
let result = withDefault<?Nat>(null, pure func () : Nat = 0);

Predicate Pattern

Define predicates inline:

func findFirst(arr : [Nat], pred : Nat -> Bool) : ?Nat {
// implementation
};

// Usage
let firstEven = findFirst(arr, pure func (n : Nat) : Bool = n % 2 == 0);

Transformation Pattern

Apply transformations to data:

func mapArray(arr : [Nat], f : Nat -> Nat) : [Nat] {
// implementation
};

// Usage
let doubled = mapArray(arr, pure func (n : Nat) : Nat = n * 2);

Restrictions

Anonymous functions have these restrictions:

ContextRestriction
Higher-order valuesMust be pure or contract-typed in verification mode
QuantifiersMust use pure func
Array accessDo not close over actor arrays or hidden mutable array state; guarded array predicates are supported in quantifiers
State mutationNot allowed in pure functions
Recursive pure callsRejected; use methods/lemmas or trusted helpers

Common Mistakes

Missing Pure Modifier or Contracts

// WRONG - missing pure/contract for higher-order use
let f = func (x : Nat) : Nat { x + 1 };

// CORRECT
let f = pure func (x : Nat) : Nat { x + 1 };

// CORRECT - or use a contract-typed callback
type Step = (x : Nat) -> Nat contract { ensures result == x + 1; };
let f2 : Step = func (x : Nat) : Nat
ensures result == x + 1;
{ x + 1 };

Accessing State in Pure Function

persistent actor {
var counter : Nat = 0;

public func bad() : async () {
// WRONG - pure functions cannot access fields
let f = pure func () : Nat { counter }; // Error M0317
};
}

Non-Expression Quantifier Predicate

// WRONG - quantifier needs expression body
assert forall<Nat>(pure func (i : Nat) : Bool {
let x = arr[i];
x > 0
});

// CORRECT - use expression body for simple predicates
assert forall<Nat>(pure func (i : Nat) : Bool =
i < arr.size() ==> arr[i] > 0);

Summary

  • Anonymous functions use func (params) : Type { body } without a name
  • Expression body form = expr is concise for simple functions
  • Higher-order use in verification mode requires pure or contract-typed callbacks
  • Closures capture variables from the enclosing scope
  • Anonymous functions support full verification contracts
  • Quantifiers use pure func predicates with expression body syntax
  • Function types use arrow notation: Nat -> Bool, (Int, Int) -> Int