19. Refinement, Override, and Drop
Once types stamp content onto instances and onto subtypes, the natural next question is: how do downstream things edit what they were given?
Archlang’s answer is three operations — refinement, override, and drop — that apply uniformly along two axes:
- Type-level: a subtype modifying what its parent type provides.
- Instance-level: an instance modifying what its type provides.
The operations are the same in both cases. That uniformity is the whole point of this chapter.
The three operations
| Action | Keyword | What it does |
|---|---|---|
| Refine | (none) | Redeclare an inherited entity with the same kind (or a subtype). New content merges with inherited content. |
| Override | override | Replace an inherited entity with a kind that isn’t a subtype of the original. Deliberate, requires the keyword. |
| Drop | drop | Remove an inherited entity entirely from this scope and below. |
That’s the entire vocabulary. Three keywords; both axes.
Refinement
When you redeclare an inherited entity with the same kind (or a subtype of it), the operation is refinement — and you don’t need a keyword. New content merges with inherited content:
type module service { component metrics { command Emit } // inherited}
service Orders { // Refining metrics — same kind, merges: component metrics { command EmitStructured // adds; Emit is still inherited }}Orders.metrics ends up with both Emit (inherited) and EmitStructured (added). Refinement is the lightest-weight operation; it’s what you reach for when you’re adding to what was given.
Refinement may also tighten — redeclare an inherited required blank with a subtype kind, keeping the blank:
type module service { required database PrimaryStore}
type service transactionalService { required relational_db PrimaryStore // relational_db extends database // still blank, narrower kind}Same kind family (relational_db is a subtype of database), so the keyword isn’t needed.
Override
When you redeclare an inherited entity with a kind that is not a subtype of the original, you need the override keyword. It’s a deliberate signal: I’m replacing this with something different.
type module service { component metrics { command Emit } // inherited}
service Reports { // Switching from 'component' to 'database' — not a subtype relationship. // Without 'override', the validator rejects this. override database metrics { labels { storage: postgres } }}override can keep the result blank by combining with required:
type module service { required database PrimaryStore}
service CacheOnly { override required cache PrimaryStore // switch to cache kind, stay blank}Why the keyword is mandatory. Refinement is meant to read smoothly — adding a command to an inherited component shouldn’t be visually noisy. But silently swapping a
componentfor adatabasewould hide what’s actually happening. Theoverridekeyword forces the author to acknowledge “I’m doing something different from refinement here,” and forces reviewers to see it. Analogous to needingunsafein Rust — visually loud where the semantics are unusually permissive.
override is a clean-slate kind switch
When you override to a new kind, the new declaration’s lineage is the new kind’s template, not the old. Any drop X.Y inside an overrided body refers only to children of the new kind. The old kind’s content is gone, not merged in.
type module service { component metrics { command Emit; command Flush }}
service Reports { override database metrics { // 'metrics' is now a database — no Emit, no Flush. The component // template is not in scope here. Lineage starts from 'database'. command Read command Write }}Redundant override
Using override where refinement would suffice (same kind or subtype) is allowed but flagged as redundant by tooling. Same idea as Java’s public on interface methods — the keyword preserves intent; the warning prevents rust.
Drop
drop X removes an inherited entity entirely from this scope and from descendants:
type module service { component metrics { command Emit } required cascade team}
service TeamlessReports { drop team // no 'team' field at all in this subtree drop metrics // no 'metrics' component either}drop X.Y removes a specific child:
service Reports { component metrics { drop Emit // removes the inherited Emit command CollectBatch // adds a new one }}drop is absolute on cascade
A drop doesn’t shadow; it breaks the cascade chain at that point. Descendants do not resume reading from a deeper ancestor. Covered in Chapter 18.
drop only applies to inherited things
Dropping something that doesn’t exist in scope is a validation error:
type module service { component metrics { command Emit }}
service Bad { drop antlers // ❌ 'antlers' was never inherited; nothing to drop}Invariants
The validator enforces these:
overrideonly applies to inherited entities. Using it on a fresh declaration is an error.droponly applies to inherited entities. Dropping something not in scope is an error.requiredand content are mutually exclusive.required component logs { command Send }is a contradiction — required means no value; the brace block means here’s a value. The validator rejects this.
Three diagnostics, one for each invariant:
type module service { component metrics { command Emit }}
service Bad { override component log { command Write } // ❌ 'log' was never inherited drop antlers // ❌ 'antlers' was never inherited required component logs { command Send } // ❌ 'required' + content}At the type level
Same three operations, applied by a subtype to a parent type:
type module service { required cascade team required database PrimaryStore component metrics { command Emit }}
// Subtype refining a required blank to a narrower kind:type service transactionalService { required relational_db PrimaryStore // refine to subtype kind, keep blank}
// Subtype overriding to a non-subtype kind:type service cacheBackedService { override required cache PrimaryStore // override, keep blank}
// Subtype fulfilling a requirement:type service paymentsService { team: "Payments" // fulfills 'required cascade team' database PrimaryStore { command Charge } // fulfills required section}
// Subtype dropping a default sub-declaration:type service slimService { drop metrics}
// Subtype dropping the whole declaration:type service teamlessService { drop team}A subtype, like an instance, can only use these operations on entities it inherited from a parent type. Subtypes may also add fresh declarations of their own, of course.
Subtypes can’t change cascade behavior
One restriction at the type level: subtypes can not redefine cascade/append behavior. The propagation mode is fixed by the type that introduces the field. If service declares cascade team, no subtype can make team non-cascading. This is intentional — it keeps the mental model “what does propagation do for this field?” stable across the entire type chain.
One operation per target per body
A given target — a field, label, or sub-declaration — can be addressed at most once per body. Two operations on the same target in the same body is a validation error:
service Bad { team: A team: B // ❌ two assignments to 'team'
drop metrics component metrics { ... } // ❌ drop + add on the same target}If you need “wipe and replace,” do it in two scopes (a subtype that drops, an instance that adds) or via override (which replaces in one step).
A worked diff at both levels
A scenario combining everything in this chapter.
The base type:
type module service { required cascade team required database PrimaryStore component metrics { command Emit }}A subtype refines, overrides, and drops:
type service readReplica { required relational_db PrimaryStore // refine: narrower kind, keep blank override component metrics_v2 { // ❌ 'metrics_v2' not inherited; // override requires existing entity command EmitV2 }}That second declaration is actually a fresh add — override doesn’t apply. Fix:
type service readReplica { required relational_db PrimaryStore component metrics_v2 { // ✅ fresh add, no override keyword command EmitV2 }}An instance of readReplica:
readReplica AnalyticsDB { team: Analytics relational_db PrimaryStore { // fulfill (refining kind from inherited) command Read }
component metrics { command EmitStructured // refine: adds, keeps inherited Emit }
drop metrics_v2 // remove inherited}What AnalyticsDB ends up with:
team: Analytics(fulfilled cascade blank).relational_db PrimaryStore { command Read }(fulfilled subtype-refined section).component metrics { command Emit; command EmitStructured }(refined inherited).- No
metrics_v2(dropped).
Every operation reads cleanly; nothing is implicit.
Summary
- Three operations: refine (no keyword), override (keyword required), drop (keyword required).
- Refinement merges new content with inherited content; works for same-kind or subtype redeclarations.
- Override is for kind switches that aren’t subtype refinements — deliberate, loud, mandatory keyword.
- Drop removes the inherited entity entirely; cascade chains break at the drop.
- Three invariants: override/drop only apply to inherited entities;
requiredand content are mutually exclusive. - Subtypes can use the same three operations against parent types; they cannot change cascade behavior.
- One operation per target per body.
What’s next
Chapter 20: Widgets → — the final chapter, on how custom rendering plugs into the model.