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