19. Уточнение, переопределение и отбрасывание
Раз типы штампуют содержимое на экземпляры и на подтипы, естественный следующий вопрос: как нижестоящие сущности редактируют то, что им дано?
Ответ Archlang — три операции: уточнение, переопределение и отбрасывание, — которые применяются единообразно по двум осям:
- На уровне типа: подтип изменяет то, что предоставляет его родительский тип.
- На уровне экземпляра: экземпляр изменяет то, что предоставляет его тип.
В обоих случаях операции одни и те же. В этом единообразии и есть весь смысл этой главы.
Три операции
| Действие | Ключевое слово | Что делает |
|---|---|---|
| Уточнить | (нет) | Переобъявить унаследованную сущность с тем же видом (или подтипом). Новое содержимое сливается с унаследованным. |
| Переопределить | override | Заменить унаследованную сущность видом, не являющимся подтипом исходного. Намеренно, требует ключевого слова. |
| Отбросить | drop | Полностью удалить унаследованную сущность из этой области и ниже. |
Это весь словарь. Три ключевых слова; обе оси.
Уточнение
Когда вы переобъявляете унаследованную сущность с тем же видом (или его подтипом), операция называется уточнением — ключевое слово не требуется. Новое содержимое сливается с унаследованным:
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 в итоге будут и Emit (унаследованное), и EmitStructured (добавленное). Уточнение — самая лёгкая операция; это то, к чему обращаются, когда хотят добавить к данному.
Уточнение также может сужать — переобъявить унаследованный обязательный пропуск с видом-подтипом, сохранив пропуск:
type module service { required database PrimaryStore}
type service transactionalService { required relational_db PrimaryStore // relational_db extends database // still blank, narrower kind}То же семейство видов (relational_db — подтип database), поэтому ключевое слово не нужно.
Переопределение
Когда вы переобъявляете унаследованную сущность видом, который не является подтипом исходного, нужно ключевое слово override. Это намеренный сигнал: я заменяю это чем-то другим.
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 может сохранить результат пустым, сочетаясь с required:
type module service { required database PrimaryStore}
service CacheOnly { override required cache PrimaryStore // switch to cache kind, stay blank}Почему ключевое слово обязательно. Уточнение должно читаться гладко — добавление команды к унаследованному компоненту не должно быть визуально шумным. Но молчаливая замена
componentнаdatabaseскрыла бы то, что на самом деле происходит. Ключевое словоoverrideвынуждает автора признать «здесь я делаю что-то отличное от уточнения», а ревьюеров — это увидеть. Аналогично необходимостиunsafeв Rust — визуально громко там, где семантика необычно разрешительна.
override — это смена вида с чистого листа
Когда вы override к новому виду, родословная нового объявления — это шаблон нового вида, а не старого. Любой drop X.Y внутри override-нутого тела ссылается только на детей нового вида. Содержимое старого вида исчезло, не сливается.
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 }}Избыточный override
Использование override там, где достаточно уточнения (тот же вид или подтип), допустимо, но помечается как избыточное инструментарием. Та же идея, что и public на методах интерфейса в Java — ключевое слово сохраняет намерение; предупреждение предотвращает деградацию.
Отбрасывание
drop X полностью удаляет унаследованную сущность из этой области и потомков:
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 удаляет конкретного потомка:
service Reports { component metrics { drop Emit // removes the inherited Emit command CollectBatch // adds a new one }}drop абсолютен на каскаде
drop не заслоняет; он разрывает каскадную цепочку в этой точке. Потомки не возобновляют чтение из более глубокого предка. Рассмотрено в главе 18.
drop применяется только к унаследованному
Отбрасывание того, чего нет в области видимости, — ошибка валидации:
type module service { component metrics { command Emit }}
service Bad { drop antlers // ❌ 'antlers' was never inherited; nothing to drop}Инварианты
Валидатор обеспечивает следующее:
overrideприменяется только к унаследованным сущностям. Использование на свежем объявлении — ошибка.dropприменяется только к унаследованным сущностям. Отбрасывание того, чего нет в области видимости, — ошибка.requiredи содержимое взаимно исключающи.required component logs { command Send }— противоречие: required означает нет значения; фигурный блок означает вот значение. Валидатор это отвергает.
Три диагностики, по одной на каждый инвариант:
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}На уровне типа
Те же три операции, применяемые подтипом к родительскому типу:
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}Подтип, как и экземпляр, может применять эти операции только к сущностям, которые он унаследовал от родительского типа. Подтипы, конечно, могут также добавлять свежие объявления своего собственного.
Подтипы не могут менять поведение каскада
Одно ограничение на уровне типа: подтипы не могут переопределять поведение cascade/append. Режим распространения зафиксирован типом, который вводит поле. Если service объявляет cascade team, ни один подтип не может сделать team некаскадным. Это намеренно — это сохраняет ментальную модель «что делает распространение для этого поля?» устойчивой по всей цепочке типов.
Одна операция на цель на тело
Данная цель — поле, метка или под-объявление — может быть адресована не более одного раза на тело. Две операции на одной цели в одном теле — ошибка валидации:
service Bad { team: A team: B // ❌ two assignments to 'team'
drop metrics component metrics { ... } // ❌ drop + add on the same target}Если вам нужно «стереть и заменить», делайте это в двух областях (подтип, который отбрасывает, и экземпляр, который добавляет) или через override (который заменяет за один шаг).
Проработанный дифф на обоих уровнях
Сценарий, сочетающий всё из этой главы.
Базовый тип:
type module service { required cascade team required database PrimaryStore component metrics { command Emit }}Подтип уточняет, переопределяет и отбрасывает:
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 }}Второе объявление на самом деле — свежее добавление, override к нему неприменим. Исправление:
type service readReplica { required relational_db PrimaryStore component metrics_v2 { // ✅ fresh add, no override keyword command EmitV2 }}Экземпляр 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}К чему приходит AnalyticsDB:
team: Analytics(заполнен каскадный пропуск).relational_db PrimaryStore { command Read }(заполнен раздел с уточнённым подтипом).component metrics { command Emit; command EmitStructured }(уточнено унаследованное).- Нет
metrics_v2(отброшено).
Каждая операция читается ясно; ничего не подразумевается неявно.
Резюме
- Три операции: уточнение (без ключевого слова), переопределение (требует ключевого слова), отбрасывание (требует ключевого слова).
- Уточнение сливает новое содержимое с унаследованным; работает для переобъявлений того же вида или подтипа.
- Переопределение — для смен вида, не являющихся уточнениями подтипа: намеренно, громко, с обязательным ключевым словом.
- Отбрасывание полностью удаляет унаследованную сущность; каскадные цепочки разрываются на отбрасывании.
- Три инварианта: override/drop применяются только к унаследованным сущностям;
requiredи содержимое взаимно исключающи. - Подтипы могут использовать те же три операции по отношению к родительским типам; они не могут менять поведение каскада.
- Одна операция на цель на тело.
Что дальше
Глава 20: Виджеты → — заключительная глава о том, как пользовательский рендер встраивается в модель.