What operations are forbidden inside a pure method?
← Data Flow Safety · Ref: Q635
Pure methods and functions are forbidden from using any mutation operator. This is enforced at compile time as E08120.
FORBIDDEN MUTATION OPERATORS IN PURE CONTEXT
The following operators are mutation operators and cannot appear inside a pure method or function: += (add-assign), -= (subtract-assign), *= (multiply-assign), /= (divide-assign), :=: (copy-into), :~: (merge-into), :^: (replace-with). Using any of these inside a pure method triggers E08120.
ALSO FORBIDDEN: REASSIGNMENT
Reassigning an existing local variable with : (or :=) inside a pure scope is rejected as E08100. Reassigning a parameter is E08110, in any scope.
ALLOWED IN PURE CONTEXT
Declaring a fresh variable (x <- expr) creates a new binding and is allowed. Assigning the return variable is allowed. Assign-if-unset (:=?) is allowed. Non-mutating operators (+, -, *, /, ==, <>) are allowed. Creating new values and returning them is allowed. Reading fields and parameters is allowed.
WHY THIS RESTRICTION EXISTS
Pure methods guarantee no side effects. If a pure method could use +=, it would mutate the state of an existing object, breaking the purity contract. The compiler enforces this so callers can trust that pure methods do not change any state.
NEW BINDINGS VS MUTATION
Mutation (variable += delta) modifies the existing object in place: E08120. Reassignment (variable: newValue) is also refused in a pure scope: E08100. Write the computation as fresh declarations (next <- current + delta) or as the initialiser of the return variable, as projectedBalance does.
See Q560 for pure method basics. See Q566 for pure call chain restrictions. See Q273 for purity as a security boundary.
Example
defines module qa.dataflow.puremutation defines class Account balance <- 0.0 accountName <- "unnamed" Account() -> accountName as String initialBalance as Float this.accountName: accountName this.balance: initialBalance <?- Pure: computes new balance without mutating the field. Uses the + operator (non-mutating) in the return variable's initialiser; no variable is reassigned. -?> projectedBalance() as pure -> deposit as Float <- projected as Float: balance + deposit <?- Pure: builds a formatted string from fields. No mutation operators used. -?> formatSummary() as pure <- summary as String: `${accountName}: ${balance}` <?- Non-pure: uses mutation operator to modify balance. This method correctly omits 'as pure'. -?> deposit() -> amount as Float balance += amount <?- Non-pure: uses mutation operator. -?> withdraw() -> amount as Float balance -= amount default operator ? defines program PureMutationDemo() stdout <- Stdout() acct <- Account("Savings", 1000.0) stdout.println(acct.formatSummary()) //Pure call: safe, no mutation projected <- acct.projectedBalance(500.0) stdout.println(`Projected: ${projected}`) //Non-pure: mutates the account acct.deposit(500.0) stdout.println(acct.formatSummary()) acct.withdraw(200.0) stdout.println(acct.formatSummary())
Common mistakes
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
stdout.println(acct.formatSummary().toUpperCase())
//Pure call: safe, no mutation
Correct:
stdout.println(acct.formatSummary())
//Pure call: safe, no mutation
E08120 — A pure method may not use a mutation operator: 'balance += deposit' would change the account's state. Compute the new value with the non-mutating + instead. See ek9 -h E08120 for details.
Incorrect:
<- projected as Float: balance + deposit balance += deposit
Correct:
<- projected as Float: balance + deposit
Other ways to ask this
- Why does EK9 reject mutation operators in pure methods?
- What is E08120 mutation in pure context?
- Can I use += inside a pure function?
Coming from another language?
Java: no compile-time purity enforcement. Python: no immutability guarantees. Haskell: all functions pure by default. Rust: mutable borrows are explicit. Kotlin: val prevents reassignment but does not prevent method-level mutation. EK9: E08120 rejects mutation operators and E08100 rejects reassignment in pure methods at compile time.
Keywords: immutable, E08120, operator, side-effect, mutation, migrate, restriction, assign, initialize, data-flow, safety, copy, pure, forbidden, merge, function