Skip to content

Hello! 👋 My name is Nathan Weir. This is a fun personal project for using AI to build a bespoke, domain-specific programming language. It is not a serious, professional project. This site and the language itself are largely generated via Claude Code. If you find yourself programming with Weir, have fun - but use at your own risk!

Typeclasses

Weir uses Haskell-style typeclasses for ad-hoc polymorphism — defining shared behavior across different types. Typeclasses support higher-kinded types, multi-parameter definitions, superclass constraints, and default implementations.

Use defclass to define a typeclass with its required methods:

(defclass (Show 'a)
(show : (Fn ['a] String)))
(defclass (Eq 'a)
(equal? : (Fn ['a 'a] Bool)))

Each method has a name and a type signature. The type variable ('a) represents the type that will implement the class.

Use instance to implement a typeclass for a specific type:

(instance (Show Vec2)
(defn show (v)
(str "(" (.x v) ", " (.y v) ")")))
(instance (Eq Vec2)
(defn equal? (a b)
(and (= (.x a) (.x b))
(= (.y a) (.y b)))))

A typeclass can require that its instances also implement other typeclasses. Use => prefix syntax:

;; Ord requires Eq
(defclass (=> (Eq 'a) (Ord 'a))
(<=> : (Fn ['a 'a] Ordering)))

Any type implementing Ord must also have an Eq instance. This is checked at compile time.

Functions can require typeclass constraints on their type parameters:

;; Single constraint
(declare equal? (=> (Eq 'a) (Fn ['a 'a] Bool)))
;; Multiple constraints
(declare serialize (=> (Show 'a) (Eq 'a) (Fn ['a] String)))

The compiler verifies that the concrete types used at call sites satisfy all constraints.

Instances can have constraints too — useful for generic type implementations:

;; Show for Option requires Show for the element type
(instance (=> (Show 'a) (Show (Option 'a)))
(defn show (opt)
(match opt
((Some x) (str "Some(" (show x) ")"))
(None "None"))))

Typeclasses can abstract over type constructors (types that take type parameters), not just concrete types:

;; 'f is a type constructor like Option, List, etc.
(defclass (Functor 'f)
(map : (Fn [(Fn ['a] 'b) ('f 'a)] ('f 'b))))
(instance (Functor Option)
(defn map (f opt)
(match opt
((Some x) (Some (f x)))
(None None))))

Here 'f has kind * -> * — it’s a type that takes one type parameter. This enables abstracting over containers, effects, and other parameterized types. HKT support is essential for Functor, Monad, Foldable, and similar abstractions.

Weir uses Rust-style coherence: only the module defining the type or the typeclass can define an instance. This prevents conflicting instances:

;; In the module that defines Vec2:
(instance (Show Vec2) ...) ;; OK — defines the type
;; In the module that defines Printable:
(instance (Printable Vec2) ...) ;; OK — defines the typeclass
;; In a third module:
(instance (Show Vec2) ...) ;; ERROR — orphan instance

This is more restrictive than Haskell but prevents a real class of confusing bugs where two different modules define conflicting instances for the same type/class pair.

The Ord typeclass is defined in the prelude with instances for all primitive types:

(defclass (Ord 'a)
(compare : (Fn ['a 'a] Ordering)))
;; Pre-defined instances:
;; i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, String

Weir invests in typeclass expressiveness to reduce pressure on the macro system. A simpler, purely syntactic macro system becomes viable when the type system can carry more weight:

NeedTypeclass SolutionMacro Alternative
Print any type(Show 'a) with show methodComplex code-walking macro
Generic container ops(Functor 'f) with mapOne macro per container type
Ordered comparison(Ord 'a) with compareMacro generating comparison fns
Serialization(Serialize 'a) typeclassMacro introspecting struct fields