What does 'as pure' mean on a method in EK9?

← Purity Contracts · Ref: Q560

The 'as pure' modifier on a method guarantees that it will not mutate any state through mutation operators. This is a compiler-enforced contract, not a convention.

WHAT PURE MEANS

A pure method cannot use mutation operators (+=, -=, *=, /=, :=:, :~:, :^:). It can reassign local variables with : (which creates a new value). It cannot call non-pure methods. The compiler enforces all of these rules.

HOW TO DECLARE PURE

Add 'as pure' after the method name:

  calculate() as pure
    -> x as Integer
    <- rtn as Integer: x + 1

WHAT PURE ALLOWS

Reassignment with : (creates new binding, not mutation). Creating new values with non-mutating operators (+, -, *, /). Reading fields and parameters. Returning computed values.

WHAT PURE FORBIDS

Mutation operators (+=, -=, *=, /=, :=:, :~:, :^:) are forbidden (E08120). Calling non-pure functions or methods is forbidden (E08130). These are compile-time errors, not runtime checks.

WHY PURE MATTERS

Pure methods are safe to call from any context. They cannot corrupt shared state. They enable compiler optimizations. They create auditable security boundaries.

See Q273 for purity as a security boundary. See Q54 for pure functions and Consumer vs Acceptor. See Q561 for purity inheritance rules. See Q566 for pure call chain restrictions. See Q612 for dispatcher purity matching requirements. See Q635 for forbidden mutation operators in pure methods.

See Q666 for injection in pure context.
See Q678 for purity override contract. See Q690 for pure call chain.

Example

defines module qa.purity.basics

  defines class

    Calculator
      factor <- 1

      Calculator()
        -> factor as Integer
        this.factor: factor

      //A pure method: no mutation, just computation
      compute() as pure
        -> inputValue as Integer
        <- result as Integer: inputValue * factor

      //A pure accessor: reads state without modifying it
      currentFactor() as pure
        <- rtn as Integer: factor

      //Non-pure method: uses mutation operator
      adjustFactor()
        -> delta as Integer
        factor += delta

      default operator ?

  defines program

    PureMethodBasicsDemo()
      stdout <- Stdout()

      calc <- Calculator(3)
      stdout.println(`Factor: ${calc.currentFactor()}`)

      result <- calc.compute(10)
      stdout.println(`3 * 10 = ${result}`)

      //Non-pure: mutates factor
      calc.adjustFactor(2)
      stdout.println(`After adjust: ${calc.currentFactor()}`)

      result2 <- calc.compute(10)
      stdout.println(`5 * 10 = ${result2}`)

Common mistakes

E50001 — Renaming the variable means later references to 'calc' become unresolved, triggering E50001. See ek9 -h E50001 for details.

Incorrect:

calcXYZ <- Calculator(3)

Correct:

calc <- Calculator(3)

E50001 — Renaming the variable means later references to 'result' become unresolved, triggering E50001. See ek9 -h E50001 for details.

Incorrect:

resultXYZ <- calc.compute(10)

Correct:

result <- calc.compute(10)
Other ways to ask this
  • How do I mark a method as pure in EK9?
  • What restrictions apply to pure methods?
  • How does EK9 enforce method purity?

Coming from another language?

Java: no purity concept, any method can mutate anything. Python: no purity enforcement. Haskell: all functions pure by default (IO monad for effects). Rust: immutable borrows prevent mutation but no purity marker. Kotlin: no purity concept. EK9: 'as pure' modifier enforces no mutation operators and no non-pure calls at compile time.

Keywords: mutation, immutable, contract, operator, enforce, method, pure, side-effect, purity