Requiring an add-in to implement a published interface versus requiring only the operations you call — what does each demand of the add-in team?
answer
- who must edit whose code
- declaration versus members present
- does a build dependency appear
- a shape needs nobody's permission
- named intent versus silent match
basics
~20 sNaming a supertype demands that the add-in team edit their type's declaration and take a build dependency on your published artifact. Requiring a shape demands only that the members exist, so a type written before your host can qualify unchanged.
solid answer
~50 sA nominal constraint asks for a declaration: the candidate satisfies it only if the candidate's own declaration names the type you published. So the add-in team must be able to reference your artifact and must be free to edit the type. A structural constraint asks for a shape: the constraint lists the operations the generic body will call, and any type carrying them satisfies it with no declaration and no dependency edge. That difference decides who can play. A type written before your host existed, or owned by a team that cannot depend on you, conforms structurally at zero cost and nominally only through a hand-written adapter. What the shape gives up is intent: nothing in the candidate says it meant to be an add-in, and there is no declaration on which to hang the behavioural contract.
code
pseudocode · 20 lines// nominal: T satisfies the limit only by naming the published type
requires T is Formatter
function render(item: T) { return item.format() }
// structural: T satisfies the limit by carrying the operation
requires T has { format() -> Text }
function render(item: T) { return item.format() }
// a candidate written long before the host existed
type LegacyReport {
function format() -> Text { ... }
}
// LegacyReport satisfies the shape limit as written.
// It fails the named limit until its declaration says: is Formatter
// - or until someone wraps it:
type LegacyAdapter is Formatter {
inner: LegacyReport
function format() -> Text { return inner.format() }
}go deeper
Recall that a limit on a placeholder can be written two ways: name a type the candidate must implement, or list the operations the candidate must have.
Explain the cost to the candidate's author in each form — an edit plus a dependency on the publisher's artifact, or nothing at all — and say which pre-existing types can still qualify.
Show the consequence on a live extension point: who can ship without a code change, when an adapter is the right answer, and how a shape-matched contract stays honest in review.
Weigh reach against intent across teams. A named supertype gives you a list of conformers and a place to document and version the contract; a shape trades both away for the ability to accept code that cannot depend on you.
## What a constraint is actually asking for A generic body may only call what its constraint promises, so the constraint is the whole of the agreement between the host and a candidate type. There are two ways to write that promise. - A **nominal** constraint names a type — an abstract type the host publishes — and a candidate satisfies it only if the candidate's **own declaration** says it implements that type. - A **structural** constraint describes a **shape**: the operations, with their parameter and result types, that the body will call. A candidate satisfies it if it carries those members, whether or not anyone involved ever heard of your host. The difference is not syntax, and it is not a style preference. It is **where conformance is recorded**. Nominal conformance lives in the candidate's declaration and is written once, by its author. Structural conformance lives nowhere: it is recomputed by the checker every time that type meets that constraint. ## Who has to act Put a build system's plugin host in front of it. The host runs add-ins written by teams that may not be able to depend on your code at all — different release train, different ownership, an artifact that predates yours. 1. **A type you own.** Either form works; the nominal one costs one line in the declaration. 2. **A type another team owns but can edit.** Nominal conformance costs them an edit *and* a dependency on the artifact that declares your supertype, which is a scheduling and release-coupling decision, not a keystroke. 3. **A type nobody can edit** — generated, vendored, or frozen. Nominal conformance is unavailable directly; you write an **adapter**, a small wrapper that names your supertype and forwards each call to the wrapped value. Structural conformance is free if the members line up. 4. **A type that predates the host.** Same as above: the shape can match retroactively, the name cannot. ## What each side pays | Dimension | Requiring a named supertype | Requiring a shape | |---|---|---| | Who must edit the candidate | its author | nobody | | Build dependency on your artifact | required | none | | A type you cannot edit | adapter, or a separately declared conformance record where the platform offers one | qualifies as-is when the members line up | | Declared intent | present and searchable | absent | | Matching by accident | excluded: conformance is a statement | possible: the members are all that is checked | | Enumerating conformers | the declarations can be found | there is no declaration to find | | Renaming an operation | breaks implementers you can list | breaks conformers you cannot list | Read the last two rows together: the shape did not remove the coupling, it **moved** it. Add-ins no longer depend on your artifact, but every one of them now depends on the exact member names and signatures your constraint spells out, and you have no list of who they are. ## The middle ground Two mechanisms sit between the extremes: - **Adapters.** Keep the named requirement and absorb the cost yourself by shipping wrappers for the types you expect. Intent stays explicit; you pay maintenance per adapted type. - **Separately declared conformance.** Some type systems let conformance be declared *outside* both the type and the host — a standalone record saying "this existing type satisfies that published requirement, and here is how." That buys retroactive conformance without editing the type and keeps the requirement nominal. Its price is deciding which record wins when two parties declare one for the same pair; treated as a concept, this is the type-class style of constraint. Type systems genuinely differ here: some check only declared conformance, some check only shape, several offer both and let the constraint's author choose per requirement. The engineering question is the same in all of them. ## What an interviewer is listening for - That you answer in terms of **who must change what**, not in terms of which form looks cleaner. - That you name the **dependency edge** the named form forces on the add-in, and say it points from the add-in to the host. - That you know retroactive conformance is the shape's headline advantage, and that the adapter is the nominal answer to the same problem. - That you can state the cost of the shape without overstating it: it checks members, not meaning, so nothing in a matching type asserts that it meant to be an add-in.
- Does requiring a shape instead of a named supertype remove the coupling, or move it?It moves it. The add-in no longer references your artifact, so nothing appears in its build file. But it now depends on the exact operation names and signatures your constraint lists, and if you rename one, every conformer breaks even though none of them ever named you — and you have no declarations to search to find out who they were.
- A team hands you a type they cannot edit and your host requires a named supertype. What are your options?Wrap it: write an adapter that names your supertype and forwards each call to the wrapped value, and pass the adapter as the type argument. If the platform supports conformance declared outside the type, declare it there instead — no edit, intent preserved. Otherwise relax that one requirement to a shape, accepting that the candidate now asserts nothing about intent.
- Why can a nominal requirement carry a behavioural contract that a shape cannot?Because the declaration is a place to attach it. A published supertype has documentation, a version, and a list of implementers you can notify, and naming it is an act of agreeing to the contract. A shape is recomputed per use site; nothing in a matching type points anywhere, so the contract lives only in the host's prose.
saying these in an interview costs you the question
- Says the two forms are interchangeable sugar for the same check.
- Claims a shape requirement still needs the candidate's author to declare conformance.
- Thinks a named supertype can be satisfied by a type you cannot edit, with no adapter.
- Treats the published supertype as free, ignoring the build dependency it forces.
- Assumes matching by shape also checks behaviour rather than members.