POLYTONE — the AI-native programming language

Guide

Traits

A trait names a behaviour contract; a type implements it inside its own declaration, and a bounded generic function works for every type that honours it.

cheapest.pt
record Money:
    cents: Int

    with Ord:
        fn lt(self, other: Self) -> Bool = self.cents < other.cents

    with Display:
        fn show(self) -> Text = "${self.cents / 100}.{self.cents % 100}"

fn cheapest[T: Ord + Display](items: List[T], fallback: T) -> Text:
    mut best = fallback
    for x in items:
        if x.lt(best):
            best = x
    return "{best}"

fn main() -> Void:
    let prices = [Money(cents: 350), Money(cents: 120), Money(cents: 990)]
    print(cheapest(prices, prices[0]))
    print(cheapest([9, 3, 7], 9))

A trait declares method signatures ('trait Shape:' with 'fn area(self) -> Float' lines — docs allowed, bodies not), and a type implements it in a 'with Shape:' block after its fields and methods, holding the contract exactly: every signature present, nothing extra, same types. Self in any member signature names the implementing type. An implementation is an ordinary method — r.area() calls it, and dispatch is fully static: the compiler resolves every call at compile time, so a trait costs nothing at runtime.

Bounds live on functions: inside 'fn cheapest[T: Ord + Display]' a T-typed value has exactly the bound's methods, several traits join with '+', and every call site must pass a type that implements them all — the error names the exact fix. Two traits built in and predeclared everywhere: Ord ('fn lt(self, other: Self) -> Bool') from which any sort builds, and Display ('fn show(self) -> Text'), which lets a type choose its own spelling in interpolation — without it, and for containers, the honest structural spelling stands. Int, Float and Text are Ord, those plus Bool are Display, so the same bounded function serves your types and the builtins alike; a pub type's built-in impls even travel across modules — time.Instant sorts and spells out of the box. Equality never needs a trait: == is structural for every value.

geometry.pt
/// What every shape can say about itself.
pub trait Shape:
    /// The enclosed area.
    fn area(self) -> Float

/// A circle by radius.
pub record Circle:
    r: Float

    with Shape:
        fn area(self) -> Float = 3.0 * self.r * self.r

/// Total area of any shapes.
pub fn total[T: Shape](shapes: List[T]) -> Float:
    mut sum = 0.0
    for s in shapes:
        sum = sum + s.area()
    return sum
main.pt
import geometry

record Square:
    side: Float

    with geometry.Shape:
        fn area(self) -> Float = self.side * self.side

fn main() -> Void:
    print("{geometry.total([Square(side: 2.0), Square(side: 3.0)])}")
    print("{geometry.total([geometry.Circle(r: 1.0)])}")
    print("{geometry.Circle(r: 2.0).area()}")

Traits travel (since 0.42). A 'pub trait' is part of its module's surface, and an importer names it module-qualified like every imported item: 'with geometry.Shape:' implements it, '[T: geometry.Shape]' bounds by it — a bare 'with Shape:' in main.pt names nothing. A 'pub fn' may carry any bound an importer can name (a built-in, the module's own pub trait, an imported one; a private trait is the one refusal, with the fix named), and any type that honours the trait satisfies the imported call: Square above, geometry's own Circle through the 'with' block it exported, a scalar under Ord or Display, or a parameter carrying the same bound. A pub type's 'with'-block methods are callable by importers with no 'pub' on each — the contract is total. Dispatch stays static: each instance is generated where its template lives, calling the implementing type's own method.