POLYTONE — the AI-native programming language

Guide

Methods

Records and enums can own their behaviour: methods are declared after the fields or variants, in the same block, and take the receiver as a bare self.

rect.pt
record Rect:
    w: Float
    h: Float

    /// The enclosed area.
    fn area(self) -> Float = self.w * self.h

    /// A rectangle `k` times the size.
    fn scaled(self, k: Float) -> Rect = Rect(w: self.w * k, h: self.h * k)

    /// One line describing it.
    fn label(self) -> Text = "{self.w}x{self.h} = {self.area()}"

fn main() -> Void:
    let r = Rect(w: 3.0, h: 4.0)
    print(r.label())
    print(r.scaled(2.0).label())

The first parameter is always the bare self — never annotated, never omitted. Fields come first, then methods, so a reader meets the shape of a type before its behaviour. One name means one thing: a method may not share a name with a field, and no two methods of one record may share a name. Two different records may of course both have area() — the receiver's type picks it.

Enums declare methods the same way, after their variants — and a method reaches a payload the only way anything does, by matching self. A method declared mut self rewrites its receiver instead of returning something: it returns Void, needs a block body, and the receiver must be a named binding declared mut, exactly like xs.push(v).

counter.pt
enum Status:
    Active
    Banned(reason: Text)

    /// Whether the account may act.
    fn allowed(self) -> Bool:
        match self:
            case Status.Active:
                return true
            case Status.Banned(reason: _):
                return false

record Counter:
    total: Int

    /// Adds `n` to the running total.
    fn bump(mut self, n: Int) -> Void:
        self.total = self.total + n

fn main() -> Void:
    mut c = Counter(total: 0)
    c.bump(5)
    print("{c.total} {Status.Active.allowed()}")