How do I implement a constructor in EK9?

← Classes and OOP · Ref: Q94

Constructors in EK9 share the name of the class. You can define multiple constructors with different parameter lists. Properties are initialised using the colon assignment operator.

BASIC CONSTRUCTOR

A constructor takes parameters with '->' and assigns to properties:

  Account()
    -> holder as String
    this.holder: holder

The 'this.' prefix disambiguates property from parameter when names match.

PROPERTY INITIALIZATION

Properties can have default values or be set in the constructor:

  balance <- 0.0              default value at declaration
  holder <- String()          starts unset, must be set in constructor

MULTIPLE CONSTRUCTORS

Overload constructors with different parameter lists:

  Account()
    -> holder as String
    this(holder, 0.0)
  Account()
    ->
      holder as String
      initialBalance as Float
    this.holder: holder
    this.balance: initialBalance

Multiple parameters use multi-line '->' blocks (comma-separated on one line is not valid). Use 'this(...)' to chain to another constructor.

COPY CONSTRUCTOR

The copy operator ':=:' copies all fields from another instance:

  operator :=:
    -> from as Account
    holder :=: from.holder
    balance :=: from.balance

DEFAULT CONSTRUCTOR

Use 'default' keyword to hide or control the no-arg constructor:

  default private Account()

This prevents external creation without parameters.

PURE CONSTRUCTORS

Mark a constructor 'as pure' to enforce immutability rules. Pure constructors use ':=?' (guarded assign) instead of ':' for field assignment. Critical rule: if ANY constructor is pure, ALL constructors must be pure (E05190).

ACCESS MODIFIERS

Constructors are public by default. Do NOT write 'public Account()' — the compiler rejects redundant 'public' (E07280). Use 'private' or 'protected' only when restricting access.

DYNAMIC CLASS CONSTRUCTORS

Named dynamic classes can define explicit constructors for reusable types:

  entry <- PersonRecord(name: n, age: a) as class
    PersonRecord()
      ->
        name as String
        age as Integer
      this.name: name
      this.age: age
    default operator ?

The captured variables become fields, and custom constructors control the interface.

See Q93 for class basics. See Q95 for field visibility. See Q104 for uninitialised properties. See Q98 for record operators. See Q118 for builder pattern. See Q580 for constructor delegation patterns. See Q563 for pure constructor assignment rules. See Q115 for dynamic classes. See Q598 for why EK9 has no default parameters and uses constructor overloading instead.

Example

defines module qa.oop.constructors

  defines class

    Account
      holder <- String()
      balance <- 0.0

      default private Account()

      Account()
        -> holder as String
        this(holder, 0.0)

      Account()
        ->
          holder as String
          initialBalance as Float
        this.holder: holder
        this.balance: initialBalance

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

      balance() as pure
        <- rtn as Float: balance

      deposit()
        -> amount as Float
        balance += amount

      operator :=:
        -> from as Account
        holder :=: from.holder
        balance :=: from.balance

      default operator ?

  defines program

    ConstructorDemo()
      stdout <- Stdout()

      // === BASIC CONSTRUCTOR ===

      account1 <- Account("Alice")
      stdout.println(`Holder: ${account1.holder()}, Balance: ${account1.balance()}`)

      // === MULTIPLE CONSTRUCTORS ===

      account2 <- Account("Bob", 100.0)
      stdout.println(`Holder: ${account2.holder()}, Balance: ${account2.balance()}`)

      // === CONSTRUCTOR CHAINING ===

      account1.deposit(50.0)
      stdout.println(`After deposit: ${account1.balance()}`)

      // === COPY OPERATOR ===

      account3 <- Account("Temp")
      account3 :=: account2
      stdout.println(`Copied: ${account3.holder()}, Balance: ${account3.balance()}`)

Common mistakes

E08180 — A bare 'holder as String' with no initializer and no injection marker '!' triggers E08180. Use '<-' with a default value, explicit type with ':' initializer, or 'as String?' for an unset field. See ek9 -h E08180 for details.

Incorrect:

holder as String

Correct:

holder <- String()

E07280 — Constructors and methods are public by default in EK9. Adding an explicit 'public' modifier is redundant and triggers E07280. Simply omit it. Use 'private' or 'protected' only when restricting access. See ek9 -h E07280 for details.

Incorrect:

public Account()
        -> holder as String

Correct:

Account()
        -> holder as String

E08130 — If ANY constructor is marked 'as pure', ALL constructors must be pure. Mixing pure and non-pure constructors triggers E05190. Also note pure constructors use ':=?' not ':' for field assignment. See ek9 -h E05190 for details.

Incorrect:

Account() as pure
        -> holder as String
        this(holder, 0.0)

Correct:

Account()
        -> holder as String
        this(holder, 0.0)
Other ways to ask this
  • What is the syntax for EK9 class constructors?
  • How do constructors work in EK9 classes?
  • Can I have multiple constructors in an EK9 class?

Coming from another language?

Java: constructors match class name, this() chaining, no named parameters, copy via clone() or copy constructor. Python: __init__(self) only, no overloading (use defaults), copy via copy.deepcopy(). Rust: no constructors, use associated functions like new(), From trait for conversion. Go: no constructors, convention is NewType() factory functions. Kotlin: primary constructor in class header, secondary with constructor keyword, copy() on data classes. EK9: constructors match class name, multiple overloads, this() chaining, ':=:' copy operator, default private constructors.

Keywords: overload, default, copy, constructor, initialise, property, object-oriented, private, parameter, chain, this, constant