Fold

fold changes tracing inside its block. Normally, primitives inside trace become IR equations. Inside the fold block, primitives are evaluated immediately and their concrete result is embedded as a literal in the surrounding trace.

import autoform as af


def program(text: str) -> str:
    with af.fold():
        prefix = af.concat("hello", " ")
    return af.concat(prefix, text)


ir = af.trace(program)("seed")

assert len(ir.ir_eqns) == 1
assert ir.call("world") == "hello world"

Without fold, the prefix concat would also be an equation. With fold, it is computed at trace time.

Trace-Time Decisions

Folded work can choose ordinary Python control flow because it runs while tracing:

def route(text: str) -> str:
    with af.fold():
        label = af.concat("priority", ": high")
    if label == "priority: high":
        return af.concat("yes: ", text)
    return af.concat("no: ", text)


ir = af.trace(route)("seed")
assert ir.call("answer") == "yes: answer"

The branch is chosen during tracing. The resulting IR contains only the path that was taken.

Dynamic Value Limits

Folded work must not depend on dynamic traced values:

def bad(text: str) -> str:
    with af.fold():
        prefix = af.concat(text, " ")
    return prefix

That raises during tracing because text is not concrete. Mark the dependency static or move the computation out of the fold block.

Outside tracing, fold is a no-op context manager.