Skip to content

Traits

Traits are ll-lang's form of type classes. They work over type constructors, not just concrete types — giving you higher-kinded abstractions like Functor and Monad.

Declaring a trait

trait Functor F =
  map(f A -> B)(fa F[A]) F[B]

The trait name (Functor) is followed by one or more type variables (here F). The body is an indented block of method signatures — no bodies, no fn.

F has kind * -> * because it's applied to A and B inside the signature. map takes a function A -> B, an F[A], and returns an F[B].

Multiple methods:

trait Monad F =
  pure(a A) F[A]
  bind(fa F[A])(f A -> F[B]) F[B]

Implementing a trait

Maybe A = Some A | None

impl Functor Maybe =
  map(f A -> B)(fa Maybe[A]) Maybe[B] =
    | Some a -> Some (f a)
    | None -> None

impl TraitName TypeName = <indented block of method decls>. Each method must match the signature declared in the trait, with F replaced by the impl type (Maybe).

Multiple impls in the same module are allowed:

impl Functor Maybe = ...
impl Monad Maybe = ...

Internally the compiler mangles impl method names as TypeName_method (so map in impl Functor Maybe becomes Maybe_map in the codegen output).

Using a constrained generic

Declare a bracket constraint before the parameter list:

transform[F: Functor](xs F[A])(f A -> B) = map f xs

Read as: "for any F that has a Functor impl". Inside the body you can call map — the compiler resolves it to the correct map_F based on the instantiated F.

Worked example from the corpus

From spec/examples/valid/04-traits.lll:

module Examples.Traits

trait Functor F =
  map(f A -> B)(fa F[A]) F[B]

trait Monad F =
  pure(a A) F[A]
  bind(fa F[A])(f A -> F[B]) F[B]

Maybe A = Some A | None

impl Functor Maybe =
  map(f A -> B)(fa Maybe[A]) Maybe[B] =
    | Some a -> Some (f a)
    | None -> None

impl Monad Maybe =
  pure(a A) Maybe[A] = Some a
  bind(fa Maybe[A])(f A -> Maybe[B]) Maybe[B] =
    | Some a -> f a
    | None -> None

transform[F: Functor](xs F[A])(f A -> B) = map f xs

Current limits

Trait calls are resolved automatically in the current compiler (including constrained generic call sites), and unresolved or ambiguous instances produce E006 MissingImpl.

Remaining limitations:

  • No superclass relations (trait Monad F : Functor F) in trait declarations.
  • No default method bodies inside trait declarations.