Skip to content

The book · 03

Composite types

book.md · 101 lines · 2 min read

Goal: declare your types with decl (one keyword for struct, enum, and interface), match sum types with pattern matching, and get to know the variant-set.

decl declares struct, enum, and interface; the body says which one:

decl Point { x: int; y: int } // fields 'name: type' → struct (product)
decl Shape { Circle(int); Square } // variants 'Name(...)' → enum (sum)
decl Drawable { fn draw() -> []byte } // signatures 'fn ...' → interface

The three share the act of declaring a contract, and the body disambiguates. Members are separated by ; or Newline, never a comma (comma is for groups in (), like arguments and tuples).

Methods are external functions with a receiver (Go style); the struct only holds data:

decl Dog { nome: string }
fn (d: *Dog) speak() -> string { return "{{d.nome}}: woof" } // receiver '*Dog'
d := Dog{ nome: "Rex" } // literal: fields by ',' (a group of values)
io.println(d.speak()) // UFCS: d.speak() ≡ speak(d)

Constructors are associated functions (receiver = just the type, no name):

fn (Dog) named(n: string) -> Dog { return Dog{ nome: n } }
rex := Dog.named("Rex") // call by dot

An enum with a payload is a sum type; match breaks it down by variant, binding the fields by position:

decl Event { Tick; Click(int, int); Quit }
fn describe(e: Event) -> string {
return match e {
Tick => "tick"
Click(x, y) => "click at ({{x}}, {{y}})"
Quit => "quit"
} // closed domain: all variants, '_' would be an error
}

An enum without a payload is a simple enumeration or a state machine:

decl State { Idle; Running; Done }
fn step(s: State) -> State {
return match s {
Idle => Running
Running => Done
Done => Done
}
}

Composition via @embeds (there is no inheritance)

Section titled “Composition via @embeds (there is no inheritance)”

Reuse is composition, with declared promotion:

decl Animal {}
fn (a: *Animal) speak() -> string { return "..." }
@embeds(Animal, methods)
decl Cat { animal: Animal; nome: string }
c := Cat{ animal: Animal{}, nome: "Mia" }
c.speak() // promoted: calls Animal.speak directly

Shape{Circle} is “a Shape, guaranteed Circle”: a set of variants. In type position, Name{...} is a variant-set; in value position, Name{...} is a struct literal (the position separates them):

fn so_clicks(e: Event{Click}) { ... } // accepts only the Click variant (checked in the type)

It is the same mechanism as the error-sets of the next chapter: a closed domain that sub-sets itself, with {...} closing the subset.

alias gives a transparent second name to a type that already exists (different from decl, which creates a new type):

alias UUID = u128 // UUID IS u128, interchangeable

Next: 04 · Errors