How do I define a class in EK9?

← Classes and OOP · Ref: Q93

Classes in EK9 are defined under a 'defines class' section. They use indentation-based syntax with no braces, no semicolons, and no explicit visibility keywords like public or private. Properties are private by default, and methods use '->' for parameters and '<-' for return values.

BASIC CLASS

Define a class with properties and methods:

  defines class
    Person
      name <- String()
      age <- Integer()

Properties use the declaration operator '<-' for type-inferred fields or explicit types.

PROPERTIES

Properties can be declared with type inference or explicit types:

  name <- "Steve"                type inferred as String
  age as Integer: 25             explicit type with initial value
  email <- String()              type inferred, starts unset

METHODS

Methods use '->' for incoming parameters and '<-' for the return:

  greet()
    -> greeting as String
    <- rtn as String: `${greeting} ${name}`

Methods are public by default. Use 'private' or 'protected' to restrict access.

CONSTRUCTOR

Constructors share the class name:

  Person()
    -> name as String
    this.name: name

DEFAULT OPERATOR

The 'default operator' keyword generates standard operators (==, <>, <=>, $, #?, ?) based on fields:

  default operator

DYNAMIC CLASSES

EK9 also supports dynamic classes defined inline. Two forms exist.

Unnamed dynamic class implements a trait inline with variable capture:

  handler <- (msg) with trait of Greeter as class
    override greetPerson()
      <- rtn as String: msg
    default operator ?

Captures variables from enclosing scope. Must override all abstract methods and include 'default operator ?' when capturing.

Named dynamic class creates a reusable type elevated to module scope:

  record <- PersonRecord(name: n, year: y) as class
    describe() as pure
      <- rtn as String: combine name and year
    default operator ?

Named dynamic classes can be used as types in function signatures and collections.

See Q22 for variable declarations. See Q49 for functions. See Q50 for function returns. See Q94 for constructors. See Q95 for field visibility. See Q96 for operators. See Q97 for records vs classes. See Q238 for the fixed operator set and enforcement rules. See Q245 for a complete example of implementing all operators on a custom type. See Q115 for dynamic classes in detail. See Q52 for dynamic functions. See Q596 for function vs method distinction.

Example

defines module qa.oop.defineclass

  defines trait

    Greeter
      greetPerson() as abstract
        <- rtn as String?

  defines class

    Person
      name <- String()
      age <- Integer()

      Person()
        ->
          name as String
          age as Integer
        this.name: name
        this.age: age

      greet()
        -> greeting as String
        <- rtn as String: `${greeting} ${name}`

      default operator

  defines program

    DefineClassDemo()
      stdout <- Stdout()

      // === BASIC CLASS ===

      person <- Person("Steve", 30)
      stdout.println(`Person: ${person}`)

      // === METHODS ===

      message <- person.greet("Hello")
      stdout.println(message)

      // === DEFAULT OPERATOR ===

      same <- Person("Steve", 30)
      stdout.println(`Equal: ${person == same}`)
      stdout.println(`Compare: ${person <=> same}`)

      // === CONSTRUCTOR ===

      another <- Person("Jane", 25)
      stdout.println(`Different: ${person <> another}`)

      // === UNNAMED DYNAMIC CLASS ===

      msg <- "Hi there"
      handler <- (msg) with trait of Greeter as class
        override greetPerson()
          <- rtn as String: msg
        default operator ?

      stdout.println(handler.greetPerson())

      // === NAMED DYNAMIC CLASS ===

      firstName <- "Carol"
      birthYear <- 1990
      entry <- PersonRecord(firstName, birthYear) as class
        describe() as pure
          <- rtn as String: `${firstName} born ${birthYear}`
        operator $ as pure
          <- rtn as String: describe()
        default operator ?

      stdout.println($entry)

Common mistakes

E06180 — Class properties are always private in EK9. You cannot access 'name' directly from outside the class; define an accessor method instead. See ek9 -h E06180 for details.

Incorrect:

person.name

Correct:

person.greet("Hello")

E08180 — Class properties must be initialized inline or marked for injection with '!'. A bare 'name as String' with no initializer 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:

name as String

Correct:

name <- String()

E07140 — A dynamic class implementing a trait must override ALL abstract methods. Omitting greetPerson() means the abstract method is unimplemented. See ek9 -h E07140 for details.

Incorrect:

handler <- (msg) with trait of Greeter as class
        default operator ?

Correct:

handler <- (msg) with trait of Greeter as class
        override greetPerson()
          <- rtn as String: msg
        default operator ?

E07236 — Dynamic classes with captured variables MUST include 'default operator ?' because captures become private fields. Without it the compiler cannot determine the set/unset state. See ek9 -h E07236 for details.

Incorrect:

handler <- (msg) with trait of Greeter as class
        override greetPerson()
          <- rtn as String: msg

Correct:

handler <- (msg) with trait of Greeter as class
        override greetPerson()
          <- rtn as String: msg
        default operator ?
Other ways to ask this
  • What is the syntax for creating a class in EK9?
  • How do classes work in EK9?
  • What does a basic EK9 class look like?
  • How do I create a class in EK9?

Coming from another language?

Java: class MyClass { private String name; public MyClass(String name) { this.name = name; } } with braces, semicolons, explicit visibility. Python: class MyClass: with __init__(self, name) and self.name, dynamic typing. Rust: struct + impl blocks, no inheritance, traits for behavior. Go: struct types with methods via receiver functions, no classes. Kotlin: class MyClass(val name: String) with concise primary constructor. Swift: class MyClass with init() initializer, properties with let/var, deinit for cleanup, final by default. EK9: indentation-based, no braces or semicolons, properties private by default, constructors named after class, default operator generates standard operators.

Keywords: basic, indentation, operator, syntax, default, method, new, object, create, define, constructor, class, property, swift, object-oriented, oop