Skip to content
kata / a language workbench

Methods and interfaces

An impl block attaches functions to a type. An instance method declares self as its first parameter.

kind Counter { value: Int }
impl Counter {
func add(self, amount: Int) {
self.value = self.value + amount
}
func read(self): Int {
ret self.value
}
func new(): Counter {
ret Counter { value: 0 }
}
}
let counter = Counter.new()
counter.add(7)
print(counter.read())

A method without self is static and is called on the type. Self can refer to the enclosing implementation’s type in annotations and construction.

For a call such as counter.add(7), the interpreter passes in the receiver value and writes the updated self back to the named binding afterward.

That write-back currently supports direct names. A mutating call through parent.child.add(7) can lose its update. Keep mutable receivers in direct bindings. For a nested record, explicitly replace the parent’s field after updating a local value.

type Named {
func name(self): Str
}
kind Label { text: Str }
impl Label as Named {
func name(self): Str {
ret self.text
}
}
func announce(value: Named) {
print(value.name())
}
announce(Label { text: "ready" })

type declares method requirements; impl Label as Named declares conformance. The interface is a type annotation you can use at a function boundary.

Conformance validation is incomplete for generic arguments and substituted signatures. It should not be treated as a static proof that every method call is valid.

kind Box[T] { value: T }
impl Box[@T] {
func get(self): T { ret self.value }
}
let box = Box[Int] { value: 42 }
print(box.get())

The @ marks a type parameter being bound by the implementation pattern. impl Box[Int] instead targets one concrete instantiation. Method lookup checks the concrete type first, then falls back to its generic base.

The custom iterator example implements the prelude’s ToIter[T] and Iter[T] interfaces in full.