Skip to content

Operators

The DSL is a small, deliberately constrained subset of Python. Every construct is validated at definition time; unsupported ones raise a clear error instead of miscompiling.

Lifting: A(x) and lift

A(x) (or lift(x, A) / lift(x, {A, B})) lifts an expression to a role: it says "this value is produced at A". Only A evaluates the expression; the result is located at {A} (or the given role set).

is_even = A(x % 2 == 0)     # only A computes this; located at {A}

Communication: copy, narrow, move

Operator Effect Result location
copy(x, A, B) communicate x from A to B loc(x) ∪ {B}
narrow(x, {A}) forget other locations (no communication) {A}
move(x, A, B) shorthand: narrow(copy(x, A, B), {B}) {B}

copy requires A ∈ loc(x); narrow requires the target set to be a subset of loc(x). These are checked statically, per role, at definition time:

y = move(x, A, B)   # x must be located at A; rejected otherwise

Communication operands are variables

copy/move/narrow operands must already be choreographic variables (names). Write the expression first, then move it:

b = B(x + 1)
y = move(b, B, A)

Tuple values: pack and unpack

pack(a, b, …) groups located values into a tuple value; unpack([x, y], t) pulls one apart, binding each element at its own location.

t = pack(x, y)   # element 0 located where x is, element 1 where y is
unpack([a, b], t)   # a @ loc(x), b @ loc(y)

Each role materializes a zone-filled view of a tuple — real values for the elements it holds, UNHELD elsewhere — so tuples need no communication to build or read. See Types & parameters.

Communication stays scalar

copy/move/narrow/lift take scalar (agreement) operands only. Moving a distributed tuple is not supported: unpack it, move the parts, and repack. (Each element already lives at its role, so this is rarely needed.)

Declared agreement: agree

agree(a, b, …) declares that values located at disjoint roles are equal, so they can be treated as one value located at their union — without any communication.

a = A(7)
b = B(7)
v = agree(a, b)      # v located at {A, B}; nobody sent anything

Each role keeps operating on its own operand; nothing is sent, compared, or reconciled. This is Klor's agree!: an unsafe escape hatch for agreement that was reached through means other than communication.

a = A("A's copy")
b = B("B's copy")
v = agree(a, b)      # programmer asserts they agree (they do NOT!)

If the values really differ, that is the programmer's bug — KlorPy will happily run it. Operands must be located at specific (non-agreement) roles and be pairwise disjoint; both are checked at definition time.

Role actions: Role.print

A.print(x) prints x at role A (useful while simulating).

What's not here (yet)

There is no agree! (programmer-declared agreement — the unsafe escape hatch Klor has), no defchor forward-declaration/mutual recursion, and no higher-order choreography-as-value types. These are documented in Limitations.