Skip to content

Closure factories and chained calls

The current implementation supports native owned-value closures and function factories, without translating to Python or evaluating dynamic source code.

A function that returns a function

agent ClosureDemo is Agent
  public
    ability run() -> List<Int> effects()
      factory = (offset: Int) => (value: Int) => value + offset
      add_two = factory(2)
      add_ten = factory(10)
      return [add_two(3), add_ten(3), factory(20)(1)]

The result is [5,13,21]. add_two and add_ten retain independent offset values rather than sharing a mutable capture slot. factory(20)(1) immediately calls the returned function.

Capture timing and mutable containers

Free local variables are captured by owned value when the closure is defined. Later changes to the original variable or container do not alter the captured snapshot. Each call receives another owned-value copy; changing a captured list affects only that call.

This is neither a Python-style shared mutable closure cell nor Agent instance state across requests.

Fn contracts and boundaries

Fn<P1,...,Pn,R> is a pure, positional function contract. The last type is the result. Fn<Int> takes no arguments and returns Int. Named arguments belong to the inferred full closure signature, not the erased Fn contract.

Creating a closure is pure. Calling it propagates all effects of its body. An effectful callback cannot masquerade as a pure Fn.

Closures cannot cross JSON, Host or remote Agent boundaries as public arguments or results, even when nested recursively. The example returns calculated integers, not executable closures to network callers.

Useful chains

  • factory(20)(1): create a function and call it immediately.
  • agent("Worker").run().score: read a field from an ability's returned record. The account example uses this pattern.
  • Callables in records or lists: call after field or index access. Use indexing to disambiguate a field from a built-in method, such as record["values"]().
  • Agent instance factory chains such as factory.create(...).method() are not supported by the current version.

Run and modify the example

Save the complete program as closures.aal and run from the AAL repository:

cargo run --locked --bin aalc -- run closures.aal --agent ClosureDemo --ability run --args '{}' --state-dir /tmp/aal-closures --invocation closures-1

Confirm [5,13,21], then change factory(2) to factory(5) and run with a new invocation ID. The first result becomes 8 while the second remains 13. Changing one factory call does not change another closure's captured value.

Continue with the account example to use this pattern in a multi-package project.