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!

Structs & Types

Weir uses separate forms for product types (defstruct) and sum types (deftype), following Coalton’s pattern. Together they form the language’s algebraic data type system.

Structs are fixed collections of named, typed fields:

(defstruct Vec2
(x : f64)
(y : f64))
(defstruct Enemy
(pos : Vec2)
(health : i32)
(state : EnemyState))

No fields keyword needed — defstruct is unambiguous. Fields always have names and types.

The type name is automatically a constructor function. Both positional and named arguments are supported:

;; Positional (fine for small structs)
(Vec2 1.0 2.0)
;; Named (keyword arguments — better for larger structs)
(Enemy :pos (Vec2 0.0 0.0) :health 100 :state Idle)
;; Mixed (positional first, then named)
(Enemy (Vec2 0.0 0.0) :health 100 :state Idle)

The compiler verifies keyword names match field names at compile time.

.field is a first-class accessor function (Coalton-style):

(.pos enemy) ;; access the pos field
(.x (.pos enemy)) ;; nested access
;; Chained with threading macro
(-> enemy .pos .x) ;; equivalent to (.x (.pos enemy))
;; Composable with higher-order functions
(map .pos enemies) ;; extract all positions
(filter (fn (e) (> (.health e) 0)) enemies)

Weir is immutable by default — there’s no field mutation. Instead, use update to create a modified copy:

(defn damage ((e : Enemy) (amount : i32)) : Enemy
(update e :health (- (.health e) amount)))
;; Original unchanged
(let ((e (Enemy :pos (Vec2 0.0 0.0) :health 100 :state Idle))
(e2 (damage e 10)))
(.health e) ;; => 100 (unchanged)
(.health e2)) ;; => 90
;; Multiple fields
(update enemy
:health (- (.health enemy) damage)
:state (if (< (.health enemy) 0) Dead (.state enemy)))

With arena allocation, functional update is near-zero-cost (bump allocation is a pointer increment).

Structs can be destructured in let bindings, function parameters, and match arms using keyword syntax:

(let (({:x :y} my-vec))
(+ x y))
(defn distance (({:x ax :y ay} : Vec2) ({:x bx :y by} : Vec2)) : f64
(sqrt (+ (* (- bx ax) (- bx ax)) (* (- by ay) (- by ay)))))

See Pattern Matching for full details.

Sum types (tagged unions / enums) have multiple variants, each optionally carrying data:

;; Simple enum (no data)
(deftype Direction North South East West)
;; Enum with mixed data
(deftype EnemyState
Idle
(Patrol Vec2 Vec2)
(Chase i64)
Dead)
;; Generic sum type
(deftype (Option 'a)
(Some 'a)
None)
(deftype (Result 'ok 'err)
(Ok 'ok)
(Err 'err))

Each variant is a constructor function:

(Some 42) ;; => (Option i64)
None ;; => (Option 'a)
(Patrol (Vec2 0.0 0.0) (Vec2 10.0 0.0)) ;; => EnemyState
Idle ;; => EnemyState (no-arg variant)

Sum types are consumed via exhaustive pattern matching:

(defn unwrap-or ((opt : (Option i64)) (default : i64)) : i64
(match opt
((Some val) val)
(None default)))

All variants must be handled. See Pattern Matching for details.

Both deftype and defstruct support type parameters (quote-prefixed):

(deftype (Either 'a 'b)
(Left 'a)
(Right 'b))
(defstruct (Pair 'a 'b)
(fst : 'a)
(snd : 'b))

When the compiler can’t determine which type you want (e.g., for numeric literals), use ann to constrain inference:

(ann i32 42) ;; force i32 instead of i64
(ann f32 3.14) ;; force f32 instead of f64
(ann (List Enemy) (filter alive? entities)) ;; pin a polymorphic return

ann is a constraint, not a cast — if the expression can’t be the asserted type, it’s a compile error. For actual type conversion, use conversion functions like to-f64 or to-i32.

Type definitions are private by default. Use pub to export:

(pub defstruct Vec2
(x : f64)
(y : f64))
(pub deftype (Option 'a)
(Some 'a)
None)

These types are automatically available in all modules:

TypeVariantsSource
Result 'ok 'errOk 'ok, Err 'errPrelude
OrderingLT, EQ, GTPrelude