Skip to content

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.

ActionKeywordWhat it does
Refine(none)Redeclare an inherited entity with the same type (or a subtype). New content merges with inherited content.
OverrideoverrideReplace an inherited entity with a type that isn’t a subtype of the original. Deliberate, requires the keyword.
DropdropRemove an inherited entity entirely from this scope and below.

That’s the entire vocabulary. Three keywords; both axes.

When you redeclare an inherited entity with the same type (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 { rest_create emit } // inherited
}
service Orders {
// Refining metrics — same type, merges:
component metrics {
rest_create 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 type, keeping the blank:

type module service {
required database PrimaryStore
}
type service transactional_service {
required relational_db PrimaryStore // relational_db extends database
// still blank, narrower type
}

Same type family (relational_db is a subtype of database), so the keyword isn’t needed.

When you redeclare an inherited entity with a type 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 { rest_create emit } // inherited
}
service Reports {
// Switching from 'component' to 'database' — not a subtype relationship.
// Without 'override', the validator rejects this.
override database metrics {
aspect { 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 type, 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 component for a database would hide what’s actually happening. The override keyword forces the author to acknowledge “I’m doing something different from refinement here,” and forces reviewers to see it. Analogous to needing unsafe in Rust — visually loud where the semantics are unusually permissive.

When you override to a new type, the new declaration’s lineage is the new type’s template, not the old. Any drop X.Y inside an overrided body refers only to children of the new type. The old type’s content is gone, not merged in.

type module service {
component metrics { rest_create emit; rest_create 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'.
db_read read
db_write write
}
}

Using override where refinement would suffice (same type 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 X removes an inherited entity entirely from this scope and from descendants:

type module service {
component metrics { rest_create emit }
required cascade version
}
service VersionlessReports {
drop version // no 'version' 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
rest_create collectBatch // adds a new one
}
}

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.

Dropping something that doesn’t exist in scope is a validation error:

type module service {
component metrics { rest_create emit }
}
service Bad {
drop antlers // ❌ 'antlers' was never inherited; nothing to drop
}

The validator enforces these:

  • override only applies to inherited entities. Using it on a fresh declaration is an error.
  • drop only applies to inherited entities. Dropping something not in scope is an error.
  • required and content are mutually exclusive. required component logs { rest_create 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 { rest_create emit }
}
service Bad {
override component log { rest_create write } // ❌ 'log' was never inherited
drop antlers // ❌ 'antlers' was never inherited
required component logs { rest_create send } // ❌ 'required' + content
}

Same three operations, applied by a subtype to a parent type:

type module service {
required cascade version
required database PrimaryStore
component metrics { rest_create emit }
}
// Subtype refining a required blank to a narrower type:
type service transactional_service {
required relational_db PrimaryStore // refine to subtype type, keep blank
}
// Subtype overriding to a non-subtype type:
type service cache_backed_service {
override required cache PrimaryStore // override, keep blank
}
// Subtype fulfilling a requirement:
type service payments_service {
version: "2.1" // fulfills 'required cascade version'
database PrimaryStore { db_write charge } // fulfills required section
}
// Subtype dropping a default sub-declaration:
type service slim_service {
drop metrics
}
// Subtype dropping the whole declaration:
type service versionless_service {
drop version
}

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.

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 version, no subtype can make version non-cascading. This is intentional — it keeps the mental model “what does propagation do for this field?” stable across the entire type chain.

A given target — a field, aspect, 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 {
version: "1.0"
version: "2.0" // ❌ two assignments to 'version'
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 scenario combining everything in this chapter.

The base type:

type module service {
required cascade version
required database PrimaryStore
component metrics { rest_create emit }
}

A subtype refines, overrides, and drops:

type service read_replica {
required relational_db PrimaryStore // refine: narrower type, keep blank
override component metrics_v2 { // ❌ 'metrics_v2' not inherited;
// override requires existing entity
rest_create emitV2
}
}

That second declaration is actually a fresh add — override doesn’t apply. Fix:

type service read_replica {
required relational_db PrimaryStore
component metrics_v2 { // ✅ fresh add, no override keyword
rest_create emitV2
}
}

An instance of read_replica:

read_replica AnalyticsDB {
version: "4.2"
relational_db PrimaryStore { // fulfill (refining type from inherited)
db_read read
}
component metrics {
rest_create emitStructured // refine: adds, keeps inherited emit
}
drop metrics_v2 // remove inherited
}

What AnalyticsDB ends up with:

  • version: "4.2" (fulfilled cascade blank).
  • relational_db PrimaryStore { db_read read } (fulfilled subtype-refined section).
  • component metrics { rest_create emit; rest_create emitStructured } (refined inherited).
  • No metrics_v2 (dropped).

Every operation reads cleanly; nothing is implicit.

  • Three operations: refine (no keyword), override (keyword required), drop (keyword required).
  • Refinement merges new content with inherited content; works for same-type or subtype redeclarations.
  • Override is for type 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; required and 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.

Chapter 20: Widgets → — the final chapter, on how custom rendering plugs into the model.