Type Classes

Type classes provide ad-hoc polymorphism in Monad, similar to Haskell type classes and Rust traits. They allow you to define interfaces that types can implement.

Basic Syntax

Define a type class with the class keyword:

class Functor (F : Type -> Type) {
    def map (f : A -> B) : (F A) -> F B
}

Default Implementations

Class methods can provide default implementations using :=:

class Show A {
    def show (a : A) : String := "<generic>"
}

Instances can override defaults:

instance Show I64 {
    def show x := "int"  // overrides the default
}

instance Show Bool {
    // uses default "<generic>"
}

Classes with Constraints

Type classes can require other classes as constraints:

class [Functor F] Applicative (F : Type -> Type) {
    def pure : A -> F A
    def apply : F (A -> B) -> F A -> F B
}

class [Applicative M] Monad (M : Type -> Type) {
    def bind (a : M A) (f : A -> M B) : M B
}

The [Functor F] syntax means "F must have a Functor instance".

Multiple Parameters

Classes can have multiple type parameters:

class HAdd A B C {
    def add : A -> B -> C
}

Constraints on Classes

A class can add constraints that must be satisfied:

class [HAdd A A A] Add A {
    def add : A -> A -> A
}

This means Add A requires HAdd A A A to exist.

Standard Type Classes

Functor

class Functor (F : Type -> Type) {
    def map (f : A -> B) : (F A) -> F B
}

FromListLiteral

Used for list literal desugaring:

class FromListLiteral (L : Type -> Type) {
    def cons (a : A) (L A) : L A
    def empty : L A
}

HAdd and Add

class HAdd A B C {
    def add : A -> B -> C
}

class [HAdd A A A] Add A {
    def add : A -> A -> A
}

Applicative

class [Functor F] Applicative (F : Type -> Type) {
    def pure : A -> F A
    def apply : F (A -> B) -> F A -> F B
}

Monad

class [Applicative M] Monad (M : Type -> Type) {
    def bind (a : M A) (f : A -> M B) : M B
}

From

class From T A {
    def from (t : T) : A
}

HMul

class HMul A B C {
    def mul : A -> B -> C
}

BEq

class BEq A {
    def beq : A -> A -> Bool
}

BOrd

class BOrd A {
    def cmp : A -> A -> I64
}

Show

class Show A {
    def show (a : A) : String := "<generic>"
}

Sub

class Sub A {
    def sub : A -> A -> A
}

Div

class Div A {
    def div : A -> A -> A
}

Append

class Append A {
    def append : A -> A -> A
}

DefaultValue

class DefaultValue A {
    def default : A
}

Type Class Constraints

Functions can require type class instances using bracket syntax:

def process [Functor F] (f : A -> B) (fa : F A) : F B :=
    Functor.map f fa

The [Functor F] syntax means "an instance of Functor for F must exist".

Infix Operators from Classes

You can create infix operators that reference class methods:

infix (>>=) := Monad.bind
infix (+) := I64.add
infix (*) := HMul.mul

Summary

  • Type classes define interfaces for types
  • Constraints [C A] require instances
  • Classes can depend on other classes
  • Instance chaining enables polymorphic behavior

Next, we'll learn about instances and how to implement type classes.