skip to content

A board routine needs its departure placeholder both time-orderable and fixed-width renderable - why two requirements instead of one supertype?

level: middleimportance: must knowfreq 58%

answer

  1. two capabilities, one element
  2. who is allowed to qualify
  3. types you do not own
  4. no redeclaration needed
  5. intersection of admissible types

basics

~20 s

Listing two requirements on one placeholder admits any type that already orders and already renders, unchanged. A combined supertype forces every candidate to be redeclared under it, which is impossible for types you do not own, and welds two unrelated capabilities together.

solid answer

~40 s

A requirement on a placeholder says the supplied type must be usable as that type; listing two says it must be usable as both at once. That admits every type that independently satisfies each one, with no edit to those types - which matters most when the candidate departure types are defined elsewhere. Inventing a supertype that declares ordering and rendering together works only for types declared under it, so retrofitting an existing type means editing its declaration, and a second routine needing a different pair of capabilities needs a second invented supertype. The requirement list also says exactly what the body calls, while a bundled supertype hides that behind one name. The cost of the list is verbosity, and the fact that at most one of the combined requirements may carry implementation.

code

pseudocode · 8 lines
pseudocode
function buildBoard<D requires Orderable, Renderable>(departures)
    ordered = sortUsing(departures, compare(a, b) = a.departsBefore(b))
    lines = empty list
    for each d in ordered
        append d.toFixedWidthLine(40) to lines
    return lines

// any departure type that already orders and renders qualifies, unchanged

go deeper

for a junior

Recall that a placeholder with no requirement can only be stored and moved; you call an operation on it only after the signature demands the contract that declares it.

for a middle

Explain the two sets that move in opposite directions: more requirements means fewer admissible type arguments and more callable operations. Then say why a list beats a bundled supertype for types you do not own.

for a senior

Show the judgment in a real API: require exactly what the body calls, and treat a requirement nobody calls as a defect that silently excludes callers you wanted.

for a principal

Own the rule for the codebase: when a repeated combination earns a name, who may introduce it, and what the migration costs the teams whose types must then be redeclared.

## The setting A departure board routine takes a list of departures and produces the lines the board shows. Its body does exactly two things with each element: it puts the departures in time order, and it turns each one into a line of fixed width. Nothing else about a departure matters to it. The routine is generic - written against a placeholder rather than a concrete departure type - because several parts of the system carry their own departure representation. The design question is what that placeholder must be required to satisfy. ## What a list of requirements means A **requirement** on a placeholder is a promise the caller must keep about the type argument it supplies: whatever type goes here must be usable as that type. Listing two of them means the argument must be usable as **both at the same time**, satisfying each independently. Adding a requirement moves two sets in opposite directions: - the set of **types that may be supplied** is the intersection of the sets each requirement admits, so it gets **smaller** with every requirement added; - the set of **operations the body may call** is the union of the members each requirement declares, so it gets **larger**. That is the entire trade. A placeholder with no requirement admits every type and lets the body do nothing with a value except move it around; each requirement buys operations by spending admissible types. This is why the honest rule for writing a bound is: require exactly the capabilities the body calls, and no more. ## The alternative: invent a supertype that declares both The other way to reach both capabilities is to declare one new type that carries ordering and rendering together, and require only that. | | Two requirements on one placeholder | One invented supertype declaring both | |---|---|---| | Who may be supplied | any type that independently satisfies both | only types declared under the new supertype | | A candidate type you do not own | nothing to change; it already qualifies | cannot qualify without editing its declaration | | A second routine needing a different pair | list the pair it uses | another invented supertype | | Concept introduced into the API | none; the contract is the list | a name the domain may not actually have | | Reading the signature | longer, but states what the body uses | short, but hides what the body uses | | Implementation-carrying members | at most one of the listed requirements | one, by construction | ## Why the invented supertype usually loses - **Ownership.** A supertype only helps types that were declared under it. Departure types produced by another team, or generated from a schema, cannot be re-parented after the fact. - **Combinatorics.** With several capabilities in play, routines need arbitrary subsets of them. Naming every useful subset produces a family of near-identical supertypes that nobody can keep straight, while a requirement list costs nothing to write. - **False concepts.** A name like board entry asserts that ordering and rendering belong together as a domain idea. If they only co-occur in this one routine, the name is a fiction other readers must decode. - **Coupling.** A type that can render but has no natural order is now shut out, or forced to invent an order it does not have, just to qualify. - **It hides the body.** The signature stops telling a reviewer which capabilities the routine actually exercises. The supertype does win in one case: when the combination genuinely is a concept, it appears across many signatures, and you own the candidate types. Then the name shortens every signature and documents a real idea. ## What the requirement list costs - Signatures get longer, and so do the diagnostics a compiler prints when an argument fails to qualify. - Only one of the combined requirements may be an implementation-carrying type in a language with single implementation inheritance; the rest must be behaviour-only contracts. - The combination usually has no name you can write outside the requirement list, so passing a value on keeps the same list alive in the next signature. - Past two or three entries, the list is a signal worth reading: it often means the routine is doing more than one job. ## Deciding it in review 1. List the operations the body actually calls on the placeholder. 2. Group them by the contract that declares each one. 3. Require exactly those contracts - drop any that no call needs. 4. If one group is used by a single branch of the body, split the routine so each half declares only what it uses. 5. Introduce a named combined type only when you own the candidate types and the same group appears across many signatures.

  • The body only sorts, and the caller does the rendering. What should the requirement list be?
    Only the ordering requirement. A requirement list should name what the body actually calls; an extra entry shuts out types for no benefit and makes the signature claim a capability the routine never uses.
  • Three routines each need a different pair out of four capabilities. What does the supertype approach cost?
    One invented supertype per pair, and every candidate type redeclared under each supertype it must serve. Requirement lists cost nothing at declaration time: each routine names the pair its body uses, and no candidate type is touched.
  • Does listing two requirements instead of one make the routine slower at run time?
    No. Both are checks on the type argument made before the program runs. Where the compiled boundary keeps only one representative for the placeholder, conversions appear where the other requirement's members are reached - a cost at the boundary, not per element.

A job ad that asks for two specific skills lets anyone who already has both apply. Inventing a new job title that bundles the two skills means every qualified person must first be re-labelled by someone who has the authority to do it.

saying these in an interview costs you the question

  • Thinks a bundled supertype is always tidier, whatever owns the candidate types
  • Believes adding a requirement widens the set of types the routine accepts
  • Says an unconstrained placeholder can already be ordered or rendered
  • Requires capabilities the body never calls because they seemed related
  • Assumes any published type can be retrofitted under a new supertype