How do I write multiple pure constructors with delegation?

← Purity Contracts · Ref: Q565

Multiple pure constructors can delegate to each other using this(), just like non-pure constructors. The delegated-to constructor must also be pure (since all constructors must have consistent purity).

DELEGATION IN PURE CONSTRUCTORS

Pure constructors can call this() as the first statement:

  Address() as pure
    -> street as String
    this(street, "Unknown")
  Address() as pure
    -> street as String, city as String
    this.street :=? street
    this.city :=? city

The delegating constructor calls another pure constructor.

WHY DELEGATION WORKS

Since E05190 requires all constructors to be consistently pure, the target of this() is guaranteed to be pure. No purity violation can occur.

PATTERN: DEFAULT VALUES

Use delegation to provide sensible defaults:

  Config() as pure
    this("localhost", 8080)
  Config() as pure
    -> host as String
    this(host, 8080)
  Config() as pure
    -> host as String, port as Integer
    this.host :=? host
    this.port :=? port

See Q563 for pure constructor field assignment. See Q564 for all-or-nothing purity. See Q580 for this() delegation in general. See Q586 for pure constructor delegation with super().

Example

defines module qa.purity.multiplepure

  defines class

    Address
      street <- String()
      city <- String()
      postcode <- String()

      //Minimal constructor delegates to two-arg
      Address() as pure
        -> street as String
        this(street, "Unknown")

      //Two-arg delegates to three-arg
      Address() as pure
        ->
          street as String
          city as String
        this(street, city, "N/A")

      //Full constructor: assigns all fields
      Address() as pure
        ->
          street as String
          city as String
          postcode as String
        this.street :=? street
        this.city :=? city
        this.postcode :=? postcode

      street() as pure
        <- rtn as String: street

      city() as pure
        <- rtn as String: city

      postcode() as pure
        <- rtn as String: postcode

      default operator ?

  defines program

    MultiplePureConstructorsDemo()
      stdout <- Stdout()

      addr1 <- Address("123 High St")
      stdout.println(`${addr1.street()}, ${addr1.city()}, ${addr1.postcode()}`)

      addr2 <- Address("456 Low St", "London")
      stdout.println(`${addr2.street()}, ${addr2.city()}, ${addr2.postcode()}`)

      addr3 <- Address("789 Mid St", "Paris", "75001")
      stdout.println(`${addr3.street()}, ${addr3.city()}, ${addr3.postcode()}`)

Common mistakes

E05190 — All constructors must be consistently pure. If the full three-argument constructor is pure, the delegating one-argument constructor must also be declared pure. See ek9 -h E05190 for details.

Incorrect:

Address()
        -> street as String
        this(street, "Unknown")

Correct:

Address() as pure
        -> street as String
        this(street, "Unknown")

E08100 — In a pure constructor, direct assignment with : counts as reassignment. The guarded assignment :=? must be used for field initialization to maintain purity. See ek9 -h E08100 for details.

Incorrect:

this.street: street

Correct:

this.street :=? street
Other ways to ask this
  • Can pure constructors use this() delegation?
  • How do I chain pure constructors?
  • What is the pattern for overloaded pure constructors?

Coming from another language?

Java: constructor chaining with this() works but no purity concept. Kotlin: constructor delegation but no purity enforcement. EK9: this() delegation in pure constructors, all constructors must be pure (E05190), :=? for field assignment.

Keywords: side-effect, contract, this, purity, constant, immutable, constructor, E05190, default, pure, overload, delegation, chain