Skip to content

15. Why Types?

You’ve been using kinds (service, database, command) since Chapter 4. Every one of them came from the stdlib. This chapter is about what kinds 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 kinds you already had. Part IV is about authoring the kinds 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.

What a type is

type module service {
required cascade team
required labels.domain
}

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

The instance fills the form:

service Payments {
team: Platform
labels { 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.”

What types replace

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

Their conceptArchlang concept
RSM archetypestype module <kind>
RSM templatestype module <kind>
Sparx stereotypestype module <kind>

Three different mechanisms in three different tools, all serving the same purpose: “modules of this flavor share these defaults and these requirements.” Archlang collapses them into one mechanism with one vocabulary.

What a type body can contain

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 team
required labels.domain
// Pre-filled sub-declaration — every service gets this component
component metrics {
command 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 team) — instance must fill.
  • A mandatory blank label (required labels.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 team, 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 labels.

Subtypes are also form templates

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

// Base type.
type module service {
required cascade team
}
// Subtype — every paymentsService gets all of service's stamp PLUS this.
type service paymentsService {
team: "Payments" // fulfills the parent's blank
labels { security.zone: PCI } // adds a new label
}

A paymentsService instance inherits both templates: service’s and paymentsService’s. The instance no longer faces the required team blank — paymentsService already filled it. But the instance can still add labels, override the team, 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.

Why this design

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 an owner. You can’t pick a default owner — defaulting to “unknown” 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 team: Payments came from the paymentsService subtype template; this widget: came from service; this subscribes: 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 team 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.

How types are referenced

Types live in .arch files alongside instances:

// In acme.shared/kinds.arch
export type module paymentsService {
required cascade team
labels { 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 paymentsService from acme.shared

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

paymentsService Stripe {
team: External
}

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

Stable IDs on types

Types carry stable IDs the same way modules do:

type #t017 module paymentsService { ... }

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.

Summary

  • 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 kind; 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.

What’s next

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