Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Traits

A spec names operations over an implicit Self. An impl gives them for a type. A bound <+T: Show> lets a generic function call them. The method is a function, and the value flows in: <x | show. Dispatch is resolved while the program is checked.

// A spec, an impl, a bound, and a default method.

spec Show {
    func show(self: Self) -> String;
}

impl Show for i64 {
    func show(self: i64) -> String { <self | int_to_str }
}

func labelled<+T: Show>(label: String, x: T) -> String {
    <(label, <x | show) | add
}

spec Kind {
    func code(self: Self) -> i64;
    func labelled_code(self: Self) -> String { <self | code | int_to_str }
}

enum Hue { Red, Blue }

impl Kind for Hue {
    func code(self: Hue) -> i64 {
        of self { Red => 1, Blue => 2 }
    }
}

proc main | (exit: i32) / {IO} {
    <("n=", 7) | labelled | println;
    <Hue::Blue | code | println;
    <Hue::Red | labelled_code | println;
    <0 | exit>
}
n=7
2
1

A method

spec Show {
    func show(self: Self) -> String;
}

impl Show for i64 {
    func show(self: i64) -> String { <self | int_to_str }
}

The parameter is named self by convention. The impl repeats the signature with Self replaced by i64. <7 | show selects that impl from the type of 7.

A generic function states the bound and calls the method the same way:

func labelled<+T: Show>(label: String, x: T) -> String {
    <(label, <x | show) | add
}

<("n=", 7) | labelled fixes T to i64. Several bounds on one parameter are joined with +, as in <+T: Ord + Display>. Each bound is checked on its own. A call that never fixes a trait parameter is refused.

One impl is allowed for a given trait, its arguments, and the implementing type. A second impl Show for i64 is refused.

A default method

A method may have a body in the spec. An impl that omits it receives that body, with Self substituted.

spec Kind {
    func code(self: Self) -> i64;
    func labelled_code(self: Self) -> String { <self | code | int_to_str }
}

impl Kind for Hue writes code only. <Hue::Red | labelled_code runs the default, which calls code and prints 1. A default may call another method of the same spec. The prelude's Eq.ne is the negation of eq, and an impl may still override it.

Parameters and associated types

A spec may take type parameters. Self stays the value that flows in. Into is the prelude's example: Into<+U> converts Self into U, and the type expected of the call chooses U.

func as_i64(n: i32) -> i64 { <n | into }

<7 | as_i64 prints 7. The conversion keeps the number when it is exact for the destination. A value that does not fit is an arithmetic overflow. The widths and the float operations are the next program.

// Into keeps a number when it is exact for the destination.

func as_i64(n: i32) -> i64 { <n | into }

func widen(n: i64) -> f64 { <n | into }

proc main | (exit: i32) / {IO} {
    <7 | as_i64 | println;
    <4 | widen | println;
    <9.0 | sqrt | println;
    <-1.5 | abs | println;
    <1.2 | floor | println;
    <1.2 | ceil | println;
    <0 | exit>
}
7
4
3
1.5
1
2

sqrt, abs, floor, ceil, sin, ln, and pow are methods of f32 and f64. num::abs is a separate function on i64.

An associated type is chosen by the impl:

spec Walk {
    type Item;
    func step(self: Self) -> Walk::Item<Self>;
}

A use writes Walk::Item<T>: the trait's arguments first, when it has any, and the implementing type last. A bound can pin it, <+T: Walk<Item = i64>>. The worked program is examples/basics/associated.sl. A supertrait is spec Rank: Eq, and an impl of Rank is accepted when Eq is implemented for the same type. See examples/basics/supertraits.sl.

The traits reference records the rules for dictionaries, overlapping impls, and associated types.