What are mutation operators and how do they differ from pure operators?

← Operators and Expressions · Ref: Q241

Mutation operators modify an object in place. They CANNOT be marked pure and MUST NOT return a value. This is the opposite of arithmetic operators like + which create NEW values and MUST be pure.

COMPOUND ASSIGNMENT

The +=, -=, *=, /= operators modify the object in place. They take 1 argument and return nothing.

  score += 10
  balance -= withdrawal
  count *= factor

These are equivalent to reassigning: score: score + 10, but more concise.

INCREMENT AND DECREMENT

The ++ and -- operators take 0 arguments and return nothing.

  counter++
  remaining--

COPY, MERGE, REPLACE

The :=: (copy), :~: (merge), and :^: (replace) operators are mutation operators. They take 1 argument and return nothing.

  target :=: source
  config :~: partial
  record :^: replacement

Copy overwrites all fields. Merge copies only SET fields. Replace is a full content replacement.

PIPE OPERATOR

The | (pipe) operator is a mutation operator used in stream processing. It takes 1 argument and returns nothing.

PURE VS MUTATION: THE KEY DISTINCTION

Pure operator + creates a NEW value:

  c <- a + b

The original 'a' is unchanged. A new value is created and assigned to 'c'.

Mutation operator += modifies IN PLACE:

  a += b

The object 'a' is directly modified. No new value is created.

IMPLEMENTING MUTATION OPERATORS

Mutation operators CANNOT have 'as pure' and MUST NOT have a return declaration:

  operator +=
    -> other as MyType
    this.value: this.value + other.value
  operator ++
    this.value: this.value + 1

See Q98 for record copy, merge, replace. See Q240 for pure arithmetic operators. See Q238 for the complete operator set. See Q273 for how purity prevents mutation-based security attacks.

Example

defines module qa.operators.mutation

  defines class

    Counter
      count <- Integer()

      Counter() as pure
        -> initial as Integer
        this.count :=: initial

      // Pure: creates NEW value
      operator + as pure
        -> other as Counter
        <- rtn as Counter: Counter(count + other.count)

      // Mutation: modifies IN PLACE
      operator +=
        -> other as Counter
        count += other.count

      operator ++
        count++

      operator --
        count--

      operator :=:
        -> source as Counter
        count :=: source.count

      operator $ as pure
        <- rtn as String: $count

      override operator ? as pure
        <- rtn as Boolean: count?

  defines program

    MutationDemo()
      stdout <- Stdout()

      // === PURE + CREATES NEW VALUE ===

      a <- Counter(10)
      b <- Counter(5)
      c <- a + b
      stdout.println(`a after +: ${a}`)
      stdout.println(`c (new value): ${c}`)

      // === MUTATION += MODIFIES IN PLACE ===

      a += b
      stdout.println(`a after +=: ${a}`)

      // === INCREMENT AND DECREMENT ===

      counter <- Counter(0)
      counter++
      counter++
      counter++
      stdout.println(`After 3 increments: ${counter}`)
      counter--
      stdout.println(`After decrement: ${counter}`)

      // === COPY ===

      original <- Counter(42)
      copied <- Counter(0)
      copied :=: original
      stdout.println(`Copied: ${copied}`)

Common mistakes

E07510 — Mutation operators (+=, -=, :=:, :~:, :^:, ++, --) modify the object in place and CANNOT be marked pure. Adding 'as pure' to a mutation operator triggers E07510. See ek9 -h E07510 for details.

Incorrect:

operator += as pure
        -> other as Counter
        count += other.count

Correct:

operator +=
        -> other as Counter
        count += other.count
Other ways to ask this
  • What is the difference between + and += in EK9?
  • How do increment and decrement work in EK9?
  • Which EK9 operators modify the object in place?

Coming from another language?

Java: += is syntactic sugar for x = x + op, ++ is increment, no copy/merge/replace operators. Python: __iadd__ for +=, no ++ operator, copy via copy.copy(). Rust: AddAssign trait for +=, no ++ operator, Clone trait for copy. Go: += is built-in, ++ is statement-only (not expression), no custom operators. Kotlin: plusAssign for +=, ++ via inc(), no merge/replace. JavaScript: += and ++ are built-in, no custom operator definitions. EK9: +=, -=, *=, /= (compound assignment), ++, -- (increment/decrement), :=: (copy), :~: (merge), :^: (replace), | (pipe), all CANNOT be pure, MUST NOT return value.

Keywords: pipe, place, immutable, replace, compound, expression, side-effect, mutate, merge, copy, assignment, mutation, modify, effect, operator, decrement, increment, side, pure