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 is a set of methods over Self. An impl supplies them for one type. A method is a function selected by the type that flows in.

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

impl Show for Bool {
    func show(self: Bool) -> String {
        of self { True => "true", False => "false" }
    }
}

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

The call is <True | show. Several parameters are one group, <(a, b) | eq. The receiver is the Self parameter, which is the first.

Coherence

An impl is keyed by the trait, its type arguments, and the implementing type. Into<i64> and Into<String> for the same type are two impls. Overlapping impls are refused. There is no impl whose Self is a bare type parameter.

A method call is accepted when a concrete type has an impl, or when a type parameter carries the bound. An unbounded type parameter does not. A trait parameter the call never fixes is refused.

Method names are unique across traits. An impl method matches the spec signature after Self and the trait arguments are substituted. An omitted trailing row argument still matches, so an impl for Stream<T> covers Stream<T, row>.

Impls exist for anonymous types too: a tuple, a sum, or the unit, keyed by the connective and the width. The prelude's Display for tuples is that form.

Bounds and defaults

func largest<+T: Ord + Display>(a: T, b: T) -> String
impl<+T: Display> Display for list::List<T>

Bounds are joined with + inside one parameter. The next parameter's sign comes after a comma. Bounds stay on positive type parameters. A trait's own parameters do not carry bounds. A data, enum, menu, or form declaration does not carry bounds.

A default body is copied into each impl that omits the method. A default may call another method of the same spec. An impl may override the default. The prelude writes Eq.ne as the negation of eq, and the integer impls override it.

Supertraits

spec Rank: Eq {
    func place(self: Self) -> i64;
}

Several parents are joined with +. A cycle is refused. An impl of the child is refused until every parent is implemented for the same type. A bound <+T: Rank> brings the parent methods as well. Ord and Eq in the prelude are separate specs: a bound that needs both names both.

Parameters and associated types

spec Into<+U> {
    func into(self: Self) -> U;
}

Self is the value that flows in. A parameter that appears in an argument is fixed by that argument. A parameter that appears only in the result, as U does, is fixed by the type the call is expected to have.

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

impl Walk for Countdown {
    type Item = i64;
    func step(self: Countdown) -> i64 { … }
}

Every impl binds each associated type. There is no default associated type and no bound on an associated type. A use is Walk::Item<T>: trait arguments first, the implementing type last. A pin is <+T: Walk<Item = i64>>. An unfixed projection is refused.

Dispatch

A call on a concrete type is a direct call to the impl. A call on a bounded type parameter takes the method from a dictionary passed with the caller. A one-method dictionary is the method. A several-method dictionary is a tuple in declaration order. There is no runtime method table.

The programs are examples/basics/traits.sl, examples/basics/defaults.sl, examples/basics/supertraits.sl, examples/basics/associated.sl, and examples/basics/into.sl.