Types
The redefinition: integers of arbitrary width,
declunifies struct/enum/interface, structural satisfaction, and two dispatches (vtable vs monomorphization) chosen by need.
The idea
Section titled “The idea”A type system does three jobs: describe data, describe contracts, and decide how
calls are dispatched. The mainstream languages take a handful of decisions here that Makoto
revisits: fixed widths (i32/i64), separate keywords (struct/enum/interface/trait),
declared satisfaction (impl Trait for T), and a fixed dispatch strategy.
How Makoto redefines it
Section titled “How Makoto redefines it”- Integers of arbitrary width:
iN/uNfor anyNfrom 1 to 65535 (u7,i128,u777).byteisu8,bitisu1, unified, the same thing conceptually. The width lives in the type and documents the intent (“this fits in 7 bits”), instead of being an accidental decision inherited from the hardware. declunifies struct, enum and interface: one keyword, and the body disambiguates (fields struct; variants enum; signatures interface). They are the same act (declaring a type) in the same realm.- Structural satisfaction: if a type has the methods the interface requires, it satisfies it. There is no
implements/impl. The type does not have to know beforehand which interfaces it will satisfy. - Two dispatches, chosen by need: an interface-value vtable (a data+vtable pair,
assembled by the compiler per type, erased); a generic constraint (
T + Trait, HKT) monomorphization (static specialization). And the strict ruler of constraints:+= implements an interface,:= belongs to a whitelist. Two symbols because they are two different questions.
The reasoning
Section titled “The reasoning”Why does decl unify three but not union? By the “operation + realm, not form” rule
(ch. 01). Struct/enum/interface have bodies of different shape and still fuse, because they are the
same verb (declaring a type) in the same realm (the type system). union has a body identical to a
struct’s and still stays out, because it is another operation (reinterpreting bytes) in another realm (the
unsafe one). Unifying union by surface similarity would be erasing a boundary.
Why structural satisfaction? Because it decouples. The implementation does not have to know the interface
to satisfy it: you create a type, and later someone discovers that it serves where the interface was
expected. The impl ritual disappears (and the noisy error of forgetting it), and the validation happens at the
point of use.
Why two dispatches instead of one? Because each one solves what the other cannot. The vtable
serves when the action does not return the concrete type (w.write(...)): a uniform fat pointer, cost at the
point of use, zero per-object overhead. But when the result depends on the type, as in map[A,B](xs: C[A]) -> C[B], which abstracts over the constructor C, the vtable is not enough; you have to monomorphize. The
ruler: an action that does not return the receiver’s type vtable; a transformation that returns it monomorphization.
Why two constraint symbols? Because one name does not distinguish “belongs to the set Foo” from
“implements the interface Foo”. : introduces only a whitelist ({List, Set}, membership); + introduces only an
interface (+ Writeable, implementation). Two symbols because they are two axes, and fusing them would reopen
the ambiguity that explicitness wants to kill.
Why not the alternatives
Section titled “Why not the alternatives”- Fixed widths (
i32/i64). They force the capacity decision into the user’s code and do not document intent;i7says whatinthides. unioninsidedecl. Different operation and realm (ch. 01); fusing them would erase the unsafe boundary.- Explicit
impl Trait for T(Rust). Coupling and ritual; forgetting theimplis a common and noisy error. Structural removes the step. - Function pointers inline in the struct (pure Zig). They would inflate each instance; the per-type vtable shares, keeping the object lean.
- Arbitrary higher-order HKT (Scala/Haskell). Arity ≤ 2 (
C[_],C[_,_]) covers most of useful FP; allowing monad transformers would open the abyss of kind inference. Powerful without being a footgun.
The concrete pain
Section titled “The concrete pain”The i32 that silently overflows because nobody decided whether it fit. The forgotten impl in Rust and the
trait-resolution error that appears three layers of instantiation below, “candidate #3 has wrong
return type”. The Zig object that fattens because it carries its own method pointers. Each one is a
type decision that leaked cost to where it should not have.
The mental model
Section titled “The mental model”The type says exactly what it is; the width lives in the type. A
decldeclares data or a contract (the body decides); satisfying is having the methods; the dispatch is a vtable when the action does not return the type, monomorphization when it does.
The accepted friction
Section titled “The accepted friction”The unification loads decl with responsibility (the body has to disambiguate three things), and
structural satisfaction trades the explicit declaration for validation at the call-site: you do not announce that
you satisfy, but you also have no silent surprise, because the compiler checks where the interface is
required. And the shallow HKT (arity ≤ 2) leaves out the higher-order transformers, expressiveness
consciously sacrificed so as not to import the inference hell.