Skip to content

Compute Blocks ​

The compute block provides automatic uncomputationβ€”operations on the compute variable are tracked and automatically reversed when the block exits.

kettle
result = compute with temp = 0
  temp += 10       -- logged
  temp *= 2        -- logged (temp = 20)
  temp             -- return value: 20
end compute        -- uncompute: temp /= 2, temp -= 10

Why Uncomputation? ​

The problem with temporary variables:

kettle
fn calculate() -> Int
  temp = 5
  temp = temp * 2   -- old value lost
  temp = temp + 3   -- old value lost
  temp
end fn

Regular assignment destroys information. Sometimes you need temporary state that gets cleaned upβ€”without losing the result.

The solution:

kettle
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 fn

How It Works ​

  1. Initialize: Create the compute variable with initial value
  2. Execute: Run the body, logging reversible operations
  3. Capture: The last expression becomes the return value
  4. Uncompute: Replay logged operations in reverse order
  5. Cleanup: The compute variable goes out of scope

Tracked Operations ​

OperationInverse
x += ex -= e
x -= ex += e
x *= cx /= c (Float only)
x /= cx *= c (Float only)
x ^= ex ^= e (self-inverse)
x <-> yx <-> 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:

kettle
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 fn

This 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:

kettle
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 fn

Control Flow ​

Conditionals and loops work inside compute blocks. The log captures whatever actually executes:

kettle
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 fn

Scope Isolation ​

The compute variable exists only inside the block:

kettle
fn scoped() -> Int
  compute with temp = 42
    temp + 1
  end compute

  -- temp is not accessible here
  -- temp  -- Error: undefined variable
  0
end fn

Practical Example: Simulation Preview ​

Compute a future state without modifying the original:

kettle
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 fn

Quantum 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:

kettle
-- 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 fn

Manual Uncomputation ​

You can manually apply inverse gates:

kettle
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 fn

This 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:

kettle
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 fn

Gate Inverse Rules ​

The compute block knows how to invert each gate:

Gate TypeInverse
Self-inverse (H, X, Y, Z, CNOT, CZ, SWAP)Same gate
S, T gatesS†, 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:

kettle
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 fn

The 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:

kettle
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 fn

Combining with is Reversible ​

You can combine both:

kettle
fn safe_oracle(q: Qubit) -> Qubit is Reversible with Quantum
  compute with ancilla: Qubit = 0
    ancilla <- hadamard
    (q, ancilla) <- cnot
  end compute
  q
end fn
  • is Reversible: Compile-time check that all operations are reversible
  • compute: 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:

kettle
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 fn

Next Steps ​

  • Gates β€” Gate inverses reference
  • Capabilities β€” The is Reversible compile-time check