Skip to main content

Classes

A class combines data (properties) and behavior (methods) into a single named type. Use the class keyword to declare one.

Declaration

class Vec2 {
x: Float
y: Float
}

Create an instance by calling the class name as a constructor:

var p = Vec2(x: 1.0, y: 2.0)

Properties

Stored Properties

Stored properties hold a value as part of the instance. A stored property may declare a default value.

class Button {
label: String = "Click Me"
width: Float
height: Float
}

Properties without a default value must be supplied when creating an instance.

Computed Properties

Computed properties derive their value from other properties. They are part of the language's reactivity system: a computed property is re-evaluated automatically when one of its dependencies changes, not on every access.

Read-only

A body written directly after the type creates a read-only computed property:

class Circle {
radius: Float = 1.0

circumference: Float {
return 2.0 * 3.14159 * self.radius
}
}

Read-write

Use explicit get and set blocks to make a computed property writable:

class Slider {
raw_value: Float = 0.0

percentage: Float {
get { return self.raw_value * 100.0 }
set(p) { self.raw_value = p / 100.0 }
}
}

Constructors

Default Constructor

Komplete Script generates a default constructor for every class. It accepts values for all stored properties as named arguments. Properties with default values are optional in the call.

class Vec2 {
x: Float
y: Float
}

var v = Vec2(x: 0.5, y: 1.0)

Custom Constructors

Define a custom constructor with the constructor keyword. It must delegate to the default constructor using : (…) to ensure all stored properties are initialized.

Constructors follow the same parameter rules as functions: parameters can have custom argument labels, be made positional with _, or be given default values. See Functions for the full parameter reference.

class Vec2 {
x: Float
y: Float
}

class BoundingBox {
top_left: Vec2
bottom_right: Vec2

constructor(x: Float, y: Float, width: Float, height: Float) : (
top_left: Vec2(x: x, y: y),
bottom_right: Vec2(x: x + width, y: y + height),
)
}
info

Adding a custom constructor hides the generated default constructor.

A custom constructor may include a body for additional setup steps:

constructor(x: Float, y: Float, width: Float, height: Float) : (
top_left: Vec2(x: x, y: y),
bottom_right: Vec2(x: x + width, y: y + height),
) {
print("BoundingBox created")
}

Multiple constructors can be defined on the same class, as long as each has a distinct parameter signature:

class Vec2 {
x: Float
y: Float
}

class BoundingBox {
top_left: Vec2
bottom_right: Vec2

constructor(x: Float, y: Float, width: Float, height: Float) : (
top_left: Vec2(x: x, y: y),
bottom_right: Vec2(x: x + width, y: y + height),
)

constructor(top_left: Vec2, bottom_right: Vec2) : (
top_left: top_left,
bottom_right: bottom_right,
)
}

var r1 = BoundingBox(x: 0, y: 0, width: 100, height: 50)
var r2 = BoundingBox(top_left: Vec2(x: 0, y: 0), bottom_right: Vec2(x: 100, y: 50))

Methods

Methods are functions declared inside a class body. They have implicit access to self.

class Counter {
count: Int = 0

increment() {
self.count = self.count + 1
}

value() -> (Int) {
return self.count
}
}

String Interpolation

By default, interpolating a class instance into a string produces a representation of its stored properties (computed properties are excluded):

import { sqrt } from math

class Vec2 {
x: Float
y: Float

length: Float {
return sqrt(self.x * self.x + self.y * self.y)
}
}

print("\{Vec2(x: 3.0, y: 4.0)}") // Prints "Vec2(x: 3.0, y: 4.0)"

Define a to_string() -> (String) method to customise this output:

import { sqrt } from math

class Vec2 {
x: Float
y: Float

length: Float {
return sqrt(self.x * self.x + self.y * self.y)
}

to_string() -> (String) {
return "(\{self.x}, \{self.y})"
}
}

print("\{Vec2(x: 3.0, y: 4.0)}") // Prints "(3.0, 4.0)"

self

Inside a class, self refers to the current instance and provides access to all its properties and methods. It is always implicitly available.

Subscript Operator

A class can support bracket subscript access (obj[expr]) by defining an at method with exactly one parameter. The parameter name and label are ignored for subscript calls — only the type matters. at is a regular method and can also be called directly:

class NumberList {
values: [Int] = []

at(_ index: Int) -> (Int) {
return self.values[index]
}
}

var list = NumberList(values: [10, 20, 30])
print("\{list[1]}") // Prints "20" — subscript syntax
print("\{list.at(1)}") // Prints "20" — direct method call, equivalent

To also support subscript assignment (obj[expr] = value), add an assign method with exactly two parameters: the assigned value first, then the subscript expression. It must not return a value. Like at, it can also be called directly:

class NumberList {
values: [Int] = []

at(_ index: Int) -> (Int) {
return self.values[index]
}

assign(_ value: Int, at index: Int) {
self.values[index] = value
}
}

var list = NumberList(values: [10, 20, 30])
list[1] = 99 // subscript assignment
list.assign(99, at: 1) // direct method call, equivalent
print("\{list[1]}") // Prints "99"

Reference Semantics

Classes have reference semantics: assigning a class instance to a variable or passing it to a function does not copy the instance — both the original and the new variable point to the same object. Mutating a property through one reference is visible through all other references to the same instance.

class Counter {
count: Int = 0
}

var a = Counter()
var b = a // b points to the same instance as a

b.count = 10
print("\{a.count}") // Prints "10" — a and b share the same instance

This makes classes well-suited as shared data models: a single instance can be declared globally or passed between components, and any mutation to it is immediately reflected everywhere that instance is used.