skip to content

When a relay hands back a payload whose type it picked itself rather than one the caller named, what changes?

level: seniorimportance: should knowfreq 40%

answer

  1. who chooses is who knows
  2. for every, versus for some
  3. the unknown moves to the other side
  4. hidden type, published operations only
  5. hiding buys the freedom to change it

basics

~20 s

The direction of the promise flips. A caller-chosen parameter obliges the implementation to work for every type; an implementation-chosen one obliges the caller to work with whatever came back, using only the operations published alongside it.

solid answer

~40 s

A universally quantified signature reads *for every type the caller names*: the caller does the choosing, so the burden of coping with an unknown sits on the implementation. Flip who chooses and the sentence becomes *for some type, which I picked and am not telling you*: the implementation knows exactly what it is working with, and now the caller is the side reasoning about an unknown. The caller can still hold the value, pass it back in, and call the operations the signature promised along with it — but it cannot name the type, cannot build another one, and cannot assume anything the signature did not publish. The implementation buys real freedom in return: it may change that hidden type later without touching a single caller, because no caller ever named it.

code

pseudocode · 11 lines
pseudocode
// caller names the type: the body must work for every name
function wrap<T>(payload: T) returns Box<T>

// implementation names the type: the caller never learns it
function openChannel() returns
    some C with { send(payload: Bytes), close() }

ch = openChannel()
ch.send(bytes)   // allowed: a published operation
ch.close()       // allowed
// no way to declare another value of C, or to name C at all

go deeper

for a junior

Recall the two readings of a signature that names no type: either the caller picks it, or the implementation picked it and is not saying which.

for a middle

Explain that flipping who chooses moves the unknown to the other side, and that a caller facing a hidden type works through the operations published with the value.

for a senior

Demonstrate the design use: hide the type when callers only pass the value back, so the implementation can replace it later without any caller-visible break.

for a principal

Own the trade in a published API. Hiding a type buys permanent freedom to change it and obliges you to anticipate every capability callers will need, because nothing can be worked around from outside.

## Two quantifiers, two beneficiaries The same missing information — *which concrete type is this?* — can be hidden from either side, and which side it is hidden from is the entire design decision. - **Caller chooses (universal).** The signature says: for **every** type you may name, this works. The caller knows the type precisely and keeps using it precisely. The implementation is the side that must be indifferent. - **Implementation chooses (existential — *for some type*).** The signature says: this returns a value of **some** type, along with these operations on it. The implementation knows exactly which type it picked. The caller is the side that must be indifferent. Both are ways of writing a signature that does not name a type. They differ only in who is left holding the uncertainty, and therefore in who gains freedom. ## The trade, side by side | | Caller chooses | Implementation chooses | |---|---|---| | Who names the concrete type | The caller, at the call site | The implementation, inside its own body | | Who must handle an unknown | The implementation | The caller | | What the other side gets | A value of exactly the type it named | A value plus the operations published with it | | Who may change the type later | The caller, freely, per call site | The implementation, without breaking callers | | What the signature hides | Nothing from the caller | The concrete type, from the caller | ## What the caller can still do with a hidden type Hiding is not paralysis. Given a value whose type the signature never names, a caller may: 1. **Hold it** — store it, keep it across calls, put it in a collection alongside others obtained the same way. 2. **Pass it back in** to the operations that came with it, which is how any real work gets done. 3. **Hand it on** to other code that also only needs those operations. What it cannot do is write the type's name in its own declarations, construct a second value of that type without going back through the implementation, or rely on properties that were never published. Two separate calls may or may not have produced the same underlying type; unless the signature says so, the caller must not assume it. ## Why an implementation would want this The hiding is bought deliberately, and the thing it buys is the freedom to change: - The concrete type can be **replaced entirely** in a later version, and no caller notices, because no caller ever wrote its name down. - The published operations become the **only** coupling surface, which is a small and deliberate one instead of an accidental one that grew from whatever callers happened to reach for. - Invariants the implementation maintains on that type **cannot be bypassed**, because nobody outside can build one or reach past the operations. The cost is symmetric and real: every capability the caller will need must be **anticipated and published**, and anything forgotten cannot be worked around from outside. A caller that needs one more operation has to come back and ask for it. ## The symmetry worth remembering The two forms are mirror images rather than opposites, and the clean way to hold them in mind is a single sentence: **the side that does the choosing is the side that knows, and the side that does not choose is the side that must cope with anything.** With a caller-chosen parameter, the body copes with anything; with an implementation-chosen one, the caller copes with anything. Stating it the other way round is the classic error in this material, and it is grammatical enough to pass unnoticed. One practical tell: if a signature names the type in its result, the caller chose it; if the result is described only by what you can do with it, the implementation chose it.

  • If the caller cannot name the returned type, how does it store the value in its own structures?
    Through whatever the signature published: usually the value is kept behind the same operations it arrived with, in a field or collection described by those operations rather than by the concrete type. Some platforms let a caller bind a local name to the unknown type for the scope of the call. What no caller gets is the real name to write freely in its own declarations.
  • Which form should a library publish for a handle a caller only ever passes back in?
    The implementation-chosen form. The caller never needs the concrete type, so naming it would only create coupling that blocks a future change. Publishing the value plus the operations it supports keeps the surface small and lets the implementation swap the underlying type later without a caller-visible break.
  • Can two calls that return a hidden type be assumed to have returned the same type?
    Not unless the signature says so. Each call independently returns a value of some type of the implementation's choosing, and a signature that only promises "some type with these operations" has not promised the two agree. Where callers need that guarantee — to put results in one collection, say — it has to be stated.

saying these in an interview costs you the question

  • Says a hidden type means the value itself cannot be stored or passed
  • Thinks the implementation is also in the dark about the type it chose
  • Claims the caller can recover the concrete type if it tries hard enough
  • Assumes two calls returning a hidden type must return the same one
  • Believes hiding the type is free and costs the implementation nothing