Skip to content

15. Why Types?

You’ve been using types (service, database, rest_create) since Chapter 4. Every one of them came from the stdlib. This chapter is about what types are and where they come from. The next four chapters are about defining your own.

This is the first chapter of Part IV. The center of gravity of the book shifts here. Part I through Part III were about authoring .arch files using the types you already had. Part IV is about authoring the types themselves.

Mindset shift. If you’re coming from an object-oriented background, the word “type” in ArchLang is not what you think. A type in ArchLang isn’t a class. A type isn’t even, really, a category. A type is a form template — a partially-filled-in document that an instance completes by filling in the blanks.

That distinction governs everything in Part IV. Hold it close.

Before the mechanics, the most important rule in this part of the book:

Rule. A plain module is the default building block; a plain interface is the default connection. Typing is sugar — reach for it only when it earns its keep.

The language is designed so that bare modules and interfaces are fine on their own. You do not need a custom type to model a service, a database, or a queue — a plain module with a good description and the right nesting says plenty.

Add a custom type only when it buys you something concrete:

  • Subtype things into a family that shares structure,
  • Attach custom widgets (rendering),
  • Define custom requirements (mandatory blanks every instance must fill),
  • Add custom fields, or
  • Explicitly assert that something is a particular type — where the statement itself is the value.

If a candidate type does none of these, don’t write it. A behaviorless service block wrapping a plain module — adding no requirement, no widget, no field — is noise. The stdlib types you’ve been using (service, database, rest_create) earn their keep precisely because they carry widgets and, sometimes, requirements. Yours should too.

type module service {
required cascade version
required aspect domain
}

That declaration says: “A service is a module that must have a version field (which cascades to descendants) and must have a domain aspect. Any service instance has to fill these in or explicitly drop them.”

The instance fills the form:

service Payments {
version: "1.0"
aspect { domain: "Payments" }
}

The type provided the form; the instance provided the content. There is no inheritance in the OOP sense — no method override, no virtual dispatch. There is template stamping: at parse time, the type’s body is stamped onto the instance as if literally written there. The instance can refine, override, or drop what was stamped, but the relationship is “this instance was poured into that form,” not “this instance is-a member of that class.”

If you’ve used architecture-modeling tools before, types replace several things at once:

Their conceptArchLang concept
Archetypestype module <type>
Templatestype module <type>
Stereotypestype module <type>

Three different mechanisms, spread across tools and often across features of the same tool, all serving the same purpose: “modules of this flavor share these defaults and these requirements.” ArchLang collapses them into one mechanism with one vocabulary.

A type body is more expressive than an instance body because it can mark things as required (mandatory blanks the instance must fill):

type module service {
// Default value — instances inherit; may override
cascade widget: arch-service
// Mandatory blank — instances must fill or drop
required cascade version
required aspect domain
// Pre-filled sub-declaration — every service gets this component
component metrics {
rest_create emit
}
// Mandatory blank sub-declaration — instance must refine or drop
required database PrimaryStore
}

Six things going on:

  • A default (cascade widget: ...) — value the instance inherits but can change.
  • A mandatory blank field (required cascade version) — instance must fill.
  • A mandatory blank aspect (required aspect domain) — instance must fill.
  • A pre-filled sub-declaration (component metrics { ... }) — instance inherits the whole thing.
  • A mandatory blank sub-declaration (required database PrimaryStore) — instance must refine.
  • A propagation modifier (cascade on version, widget) — covered in Chapter 18.

Chapter 16 walks through declaring your first type. Chapter 17 handles required. Chapter 18 is the deep dive on cascade, append, and aspects.

A type can extend another type by naming it as the parent type:

// Base type.
type module service {
required cascade version
}
// Subtype — every payments_service gets all of service's stamp PLUS this.
type service payments_service {
version: "2.1" // fulfills the parent's blank
aspect { security.zone: "PCI" } // adds a new aspect
}

A payments_service instance inherits both templates: service’s and payments_service’s. The instance no longer faces the required version blank — payments_service already filled it. But the instance can still add aspects, override the version, or drop things.

Subtypes use the same refine / override / drop operations as instances. There is no “subtype mode” vs “instance mode” — the operations are uniform. Chapter 19 handles this.

Three reasons the language went with form templates instead of classes:

Modeling needs blanks. Architecture documents have mandatory information that has no good default. Every service needs a version. You can’t pick a default version — defaulting to “unversioned” is silent failure. The right model is “the slot exists and must be filled.” That’s a blank. OOP-style classes don’t naturally express blanks; they express defaults plus override.

Stamping is debuggable. When a service’s resolved body looks unexpected, you trace it: “this version: "2.1" came from the payments_service subtype template; this widget: came from service; this repo.url came from the instance.” Template stamping is a linear chain of small additions; class inheritance produces a method-resolution-order question that’s hard to read off the source.

It composes with structural cascade. Architecture has a second propagation mechanism: values flow through nested modules (a version set on a parent flows to child components). That’s a different axis than type inheritance. Form templates compose cleanly with structural cascade — types provide the structure and any defaults, structural cascade fills in values at runtime by walking the containment tree. OOP inheritance and structural propagation don’t compose as cleanly. Chapter 18 is the chapter where this becomes clear.

Types live in .arch files alongside instances:

// In acme.shared/types.arch
export type module payments_service {
required cascade version
aspect { security.zone: "PCI" }
}

Other packages import the type via the use mechanism from Chapter 12:

// In acme.shop/package.archspace
dependencies { acme.shared: "../shared" }
use payments_service from acme.shared

Then any file in acme.shop can declare an instance:

payments_service Stripe {
version: "3.0"
}

The export modifier on the type is what makes it visible to importers. Without export, the type is internal to its declaring package.

Types carry stable IDs the same way modules do:

type #t017 module payments_service { ... }

The formatter mints them on save. Renaming a type doesn’t break its instances — the resolver matches by ID, not by type name. See Chapter 13.

  • Plain modules and interfaces are the default; typing is sugar. Add a custom type only to subtype, attach widgets, add requirements or fields, or assert “this is a type.”
  • A type is a form template — a partially-filled document an instance completes.
  • Types are not classes. There’s no method dispatch; there’s template stamping.
  • Type bodies can contain defaults, mandatory blanks (required), pre-filled sub-declarations, and propagation modifiers.
  • Subtypes extend parents by naming them as the parent type; the same refine / override / drop operations apply.
  • Form templates compose with structural cascade — the second propagation mechanism, covered in Chapter 18.
  • Types carry stable IDs; the formatter mints them on save.

Chapter 16: Defining Types → — write your first type, hands-on.