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.
ALLOWED IN PURE CONTEXT
Reassignment with : (colon) is allowed because it creates a new binding. 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.
REASSIGNMENT VS MUTATION
Reassignment (variable: newValue) binds the variable to a new value. Mutation (variable += delta) modifies the existing object in place. Pure methods allow reassignment but forbid mutation.
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 + operator (non-mutating) and : reassignment (allowed). -?> 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())
Correct:
stdout.println(acct.formatSummary())
E50001 — Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details.
Incorrect:
acct.projectedBalance(500.0)
Correct:
projected <- acct.projectedBalance(500.0)
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 in pure methods at compile time.
Keywords: assign, restriction, migrate, mutation, side-effect, operator, E08120, immutable, function, merge, forbidden, pure, copy, safety, data-flow, initialize