Skip to content

Capabilities ​

Effects (declared with with) describe what a function requires from its environment. Capabilities (declared with is) describe what a function guarantees about its behavior.

kettle
-- Effect: requires Quantum environment
fn apply_hadamard(q: Qubit) -> Qubit with Quantum
  q <- hadamard
  q
end fn

-- Capability: guarantees reversibility
fn increment(x: Int) -> Int is Reversible
  x += 1
  x
end fn

-- Both: requires Quantum, guarantees Reversible
fn quantum_oracle(q: Qubit) -> Qubit with Quantum is Reversible
  q <- hadamard
  q <- pauli_z
  q
end fn

How They Differ ​

Effects (with)Capabilities (is)
Meaning"I need this to run""I promise this property"
PropagationUpward - caller must handleDownward - caller can rely on it
EnforcementRuntime handler requiredCompile-time verification
ExamplesQuantum, IO, FailReversible, Infallible

Propagation explained:

  • Effects bubble up: if foo has with IO, then anything calling foo also needs with IO (or a handler)
  • Capabilities flow down: if foo is Infallible, callers know foo won't fail - they benefit without declaring anything

Reversible ​

A function marked is Reversible guarantees all operations can be undone. The compiler enforces this by only allowing reversible operations inside the function.

kettle
fn swap_add(x: Int, y: Int) -> Tuple[Int, Int] is Reversible
  x += y      -- reversible: inverse is x -= y
  x ^= y      -- reversible: XOR is self-inverse
  (x, y)
end fn

What's allowed:

  • Compound assignments on Int: +=, -=, ^=
  • Compound assignments on Float: +=, -=, *=, /=
  • Swaps: x <-> y
  • Quantum gates (except measure)
  • Calling other Reversible functions

Note: *= and /= on Int are forbidden because integer division loses information (e.g., 5/2=2, but 2*2=4≠5).

What's forbidden:

  • Regular assignment x = expr - destroys the old value, making reversal impossible
  • measure - collapses quantum state irreversibly
  • Calling non-reversible functions

See Reversibility for compute blocks and detailed examples.

Infallible ​

A function marked is Infallible guarantees it will always return a value - it cannot fail or panic.

kettle
fn safe_add(a: Int, b: Int) -> Int is Infallible
  a + b
end fn

fn safe_max(a: Int, b: Int) -> Int is Infallible
  match a > b
    True -> a
    False -> b
  end match
end fn

What's allowed:

  • Pure computations (arithmetic, logic, pattern matching)
  • Calling other Infallible functions
  • Operations that always succeed

What's forbidden:

  • fail(...) calls
  • try expressions (implies possible failure)
  • Calling functions that might fail

When to use Infallible:

  • Helper functions in critical paths where failure isn't an option
  • Pure mathematical operations
  • Functions that must compose without error handling
  • Building blocks for larger infallible computations
kettle
-- Building infallible pipelines
fn normalize(x: Int, min: Int, max: Int) -> Float is Infallible
  to_float(x - min) / to_float(max - min)
end fn

fn clamp(x: Int, lo: Int, hi: Int) -> Int is Infallible
  match x < lo
    True -> lo
    False -> match x > hi
      True -> hi
      False -> x
    end match
  end match
end fn

Combining Capabilities ​

A function can provide multiple guarantees:

kettle
fn pure_increment(x: Int) -> Int is Reversible, Infallible
  x += 1
  x
end fn

This function is both reversible (can be undone) and infallible (cannot fail). Both constraints are enforced by the compiler.

Combining with effects:

kettle
-- Requires Quantum, guarantees Reversible
fn phase_flip(q: Qubit) -> Qubit with Quantum is Reversible
  q <- pauli_z
  q
end fn

-- Requires IO, guarantees Infallible
fn log_message(msg: String) -> Unit with IO is Infallible
  print(msg)
end fn

The with and is clauses are independent - a function can have any combination of effects and capabilities.