Compute Blocks β
The compute block provides automatic uncomputationβoperations on the compute variable are tracked and automatically reversed when the block exits.
result = compute with temp = 0
temp += 10 -- logged
temp *= 2 -- logged (temp = 20)
temp -- return value: 20
end compute -- uncompute: temp /= 2, temp -= 10Why Uncomputation? β
The problem with temporary variables:
fn calculate() -> Int
temp = 5
temp = temp * 2 -- old value lost
temp = temp + 3 -- old value lost
temp
end fnRegular assignment destroys information. Sometimes you need temporary state that gets cleaned upβwithout losing the result.
The solution:
fn calculate() -> Int
compute with temp = 5
temp *= 2 -- logged: can undo with temp /= 2
temp += 3 -- logged: can undo with temp -= 3
temp -- capture result: 13
end compute -- temp restored to 5, then goes out of scope
end fnHow It Works β
- Initialize: Create the compute variable with initial value
- Execute: Run the body, logging reversible operations
- Capture: The last expression becomes the return value
- Uncompute: Replay logged operations in reverse order
- Cleanup: The compute variable goes out of scope
Tracked Operations β
| Operation | Inverse |
|---|---|
x += e | x -= e |
x -= e | x += e |
x *= c | x /= c (Float only) |
x /= c | x *= c (Float only) |
x ^= e | x ^= e (self-inverse) |
x <-> y | x <-> y (self-inverse) |
Note: *= and /= on Int are not reversible (integer division loses information).
Extracting Results β
Operations on the compute variable are undone. Operations on other variables persist:
fn extract_intermediate() -> Int
output = 0
compute with x = 5
x += 10 -- logged (x = 15)
output += x -- NOT logged (output is separate variable)
x *= 2 -- logged (x = 30)
x -- return value
end compute -- uncompute: x /= 2, x -= 10 (x back to 5)
output -- 15 (persisted from when x was 15)
end fnThis is the compute-copy-uncompute pattern: compute a value, copy out what you need, then uncompute to clean up.
Multiple Variables β
Track multiple variables with comma-separated syntax:
fn distance_squared(x1: Int, y1: Int, x2: Int, y2: Int) -> Int
compute with dx = 0, dy = 0
dx += x2
dx -= x1
dy += y2
dy -= y1
dx * dx + dy * dy
end compute
end fnControl Flow β
Conditionals and loops work inside compute blocks. The log captures whatever actually executes:
fn conditional(flag: Bool) -> Int
compute with x = 0
match flag
True -> x += 100 -- logged only if True
False -> x += 50 -- logged only if False
end match
x
end compute
end fn
fn with_loop() -> Int
compute with counter = 0
for i in [1, 2, 3]
counter += i -- logged 3 times
end for
counter -- 6
end compute -- uncompute: counter -= 3, -= 2, -= 1
end fnScope Isolation β
The compute variable exists only inside the block:
fn scoped() -> Int
compute with temp = 42
temp + 1
end compute
-- temp is not accessible here
-- temp -- Error: undefined variable
0
end fnPractical Example: Simulation Preview β
Compute a future state without modifying the original:
type SimState = record
position: Int
velocity: Int
end record
fn peek_future_position(initial: SimState, steps: Int) -> Int
compute with pos = initial.position, vel = initial.velocity
for _ in range(steps)
pos += vel
vel -= 1 -- gravity
end for
pos -- capture final position
end compute -- pos and vel restored
end fnQuantum Uncomputation β
In quantum computing, uncomputation means reversing operations to return qubits to their original state (usually |0>). This is needed for:
- Ancilla cleanup: Scratch qubits must return to |0> before deallocation
- Interference patterns: Uncomputed paths can interfere constructively
- Resource efficiency: Clean qubits can be reused
The Ancilla Problem β
Quantum algorithms often need temporary "scratch" qubits (ancillas). If you don't clean them up, they remain entangled with your result:
-- BAD: ancilla left entangled
fn bad_oracle(q: Qubit) -> Qubit with Quantum
ancilla: Qubit = 0
ancilla <- hadamard
(q, ancilla) <- cnot
-- ancilla is now entangled with q!
-- measuring or discarding it will collapse q's state
discard(ancilla) -- destroys quantum information
q
end fnManual Uncomputation β
You can manually apply inverse gates:
fn manual_uncompute(q: Qubit) -> Qubit with Quantum
ancilla: Qubit = 0
-- Compute
ancilla <- hadamard
(q, ancilla) <- cnot
ancilla <- t_gate
-- Uncompute (reverse order, inverse gates)
ancilla <- t_gate_dag -- Tβ
(q, ancilla) <- cnot -- CNOT is self-inverse
ancilla <- hadamard -- H is self-inverse
discard(ancilla) -- now safe: ancilla is back to |0>
q
end fnThis is error-prone and verbose. Compute blocks automate it.
Automatic with Compute Blocks β
The compute block tracks quantum gates and automatically applies their inverses when the block exits:
fn auto_uncompute(q: Qubit) -> Qubit with Quantum
compute with ancilla: Qubit = 0
ancilla <- hadamard -- tracked
(q, ancilla) <- cnot -- tracked
ancilla <- t_gate -- tracked
end compute
-- Automatically applies: Tβ , CNOT, H
-- ancilla returns to |0> and goes out of scope
q
end fnGate Inverse Rules β
The compute block knows how to invert each gate:
| Gate Type | Inverse |
|---|---|
| Self-inverse (H, X, Y, Z, CNOT, CZ, SWAP) | Same gate |
| S, T gates | Sβ , Tβ |
| Rotations (RX, RY, RZ) | Negate the angle |
Compute-Copy-Uncompute Pattern β
A common pattern: compute a value, copy out what you need, then uncompute:
fn oracle_with_phase(q: Qubit, target: Qubit) -> Tuple[Qubit, Qubit] with Quantum
compute with ancilla: Qubit = 0
ancilla <- hadamard
(q, ancilla) <- cnot
-- Apply phase based on ancilla state
(ancilla, target) <- cz
end compute
-- ancilla uncomputed, but phase kick on target persists
(q, target)
end fnThe key insight: operations on the compute variable are undone, but side effects on other qubits (like phase kicks) persist.
Multiple Ancillas β
Track multiple qubits with comma-separated syntax:
fn multi_ancilla(q: Qubit) -> Qubit with Quantum
compute with a1: Qubit = 0, a2: Qubit = 0
a1 <- hadamard
a2 <- hadamard
(q, a1) <- cnot
(a1, a2) <- cnot
(q, a2) <- cz
end compute
-- Both a1 and a2 uncomputed
q
end fnCombining with is Reversible β
You can combine both:
fn safe_oracle(q: Qubit) -> Qubit is Reversible with Quantum
compute with ancilla: Qubit = 0
ancilla <- hadamard
(q, ancilla) <- cnot
end compute
q
end fnis Reversible: Compile-time check that all operations are reversiblecompute: Runtime automation of the uncomputation
See Capabilities for more on is Reversible.
When to Use β
Use compute blocks when:
- Working with ancilla qubits that need cleanup
- Implementing oracles that should leave no trace
- Building reversible subroutines
Don't use compute blocks when:
- You need the ancilla state after the computation
- The operations aren't meant to be reversed
- You're measuring the ancilla (measurement isn't reversible)
Example: Grover Oracle β
A typical use case β mark a solution state without leaving ancilla entanglement:
fn grover_oracle(qubits: List[Qubit], target_state: Int) -> List[Qubit] with Quantum
compute with ancilla: Qubit = 1
-- Prepare ancilla in |-> state
ancilla <- hadamard
-- Multi-controlled NOT based on target_state
-- (simplified - real implementation would check each bit)
for i in range(0, length(qubits))
match bit_at(target_state, i)
1 -> (qubits[i], ancilla) <- cnot
0 -> ()
end match
end for
end compute
-- Ancilla cleaned up, target state has phase flip
qubits
end fnNext Steps β
- Gates β Gate inverses reference
- Capabilities β The
is Reversiblecompile-time check