skip to content

On the composition-and-reuse axis, what unit are you trying to identify in an unfamiliar language?

level: middleimportance: should knowfreq 44%

answer

  1. smallest thing you can reuse
  2. name it, pass it, join it
  3. what a parameter can carry
  4. can a wrapper take one and return one
  5. the glue names the unit

basics

~20 s

The thing the language lets you name, hand to other code, and join to another of its kind without editing either. Whatever fits - a routine, a bundle of behaviour, a module, a rule - is that language's unit of reuse.

solid answer

~40 s

The unit is whatever survives three probes: it can be **named**, it can be **passed and returned**, and two of them can be **joined** by some standard glue without opening either one up. So I read the sample for what appears as a parameter, what a routine hands back, and what the language's own operator or convention for wiring things together actually connects. A language whose parameters only ever carry data has routines as its unit and reuse happens by calling; one where a bundle of behaviour can be stored in a variable and passed on has a much smaller unit and composes far more freely. The answer is not what the source files are called - packaging is a separate question from what code can carry.

code

pseudocode · 11 lines
pseudocode
// the probe: can a wrapper take one and hand back one?
function retrying(unit, attempts)
    return makeUnit(input ->
        for i from 1 to attempts
            result = run(unit, input)
            if succeeded(result)
                return result
        return lastFailure())

// if this compiles, whatever `unit` holds is the unit of composition
guarded = retrying(chargeCard, 3)

go deeper

for a junior

Know that reuse has a smallest piece, and that the piece differs between languages - sometimes a routine, sometimes a larger bundle of state and behaviour.

for a middle

Apply the three probes - name it, pass it, join it - and explain why passability is the decisive one for substitution, wrapping and run-time choice.

for a senior

Show what a mismatch costs when porting: an idiom built on passing a small unit has no expression in a language whose unit cannot cross a boundary and must be rebuilt.

for a principal

Judge whether a codebase's granularity of reuse fits the language it is written in, and what a team pays long-term for fighting the language's own unit.

## What "unit" means on this axis Every style has a smallest thing you reuse. On this axis you are not asking what is *good* design in the language; you are asking a factual question about the language: **what is the smallest thing a programmer can name, hand to other code, and combine with another of its kind without editing either one?** That thing is the unit of composition, and it shapes what every library in the language looks like. The unit matters because it sets the granularity of every reuse decision. If the unit is large, reuse arrives in large pieces, and taking a small part of one means taking all of it or copying. If the unit is small and freely passable, the language's own libraries will be built by gluing small pieces, and so will yours. ## Three probes 1. **The naming probe.** Can the thing be given a name independent of where it is used? Something you can only write inline is not a unit; it is a syntax form. 2. **The passing probe.** Can it appear as a parameter and as a return value? This is the decisive one. Whatever can be handed across a boundary can be substituted, wrapped, stored in a collection and chosen at run time - and whatever cannot must be selected by writing the choice into the code instead. 3. **The joining probe.** Is there a standard way to make two of them into one - a composition operator, a wiring convention, a chain, a nesting rule - that does not require opening either one? Reuse without a joining rule is copying with extra steps. ## Signals in the language's own shape - **What the standard library is a library of.** If its contents are mostly routines, the unit is a routine. If they are mostly things you instantiate and hold, the unit is larger. - **What the common parameter types are.** A language whose parameters carry only data has already told you that behaviour is not passable. - **Whether a wrapper can exist.** If you can write something that takes a unit, returns a unit, and adds a behaviour around it, the unit is first class. If adding that behaviour requires editing the original, it is not. - **What the glue is called.** Some languages glue by calling, some by chaining a pipeline, some by inserting into a lookup chain, some by matching a request against a body of rules. The glue names the unit. ## Candidate units, and what glue joins them | Candidate unit | Joined by | Passable across a boundary? | |---|---|---| | A routine | calling one from another | yes where routines are values, no where they are only names | | A bundle of state and behaviour | holding one inside another and forwarding to it | usually yes | | A module or namespace | referring to it by name | rarely - it is a compile-time thing | | A rule in a body of rules | adding another rule the search can use | not as a value; the body of rules is the unit | | A stage in a pipeline | connecting an output to the next input | yes where a stage is itself a value | Read across that table on a sample and the score is usually obvious within a few minutes. The question that settles it is the wrapper question: **can I write something that takes one of these, returns one of these, and adds a behaviour?** ## Why the axis is not redundant The other axes tell you how state is treated, who fixes the order, and when work happens. None of them tells you the shape reuse takes. Two languages can score identically on state - both immutable-leaning - and still be entirely different to work in, because one lets you pass behaviour anywhere and the other only lets you name it at a declaration site. That difference shows up as soon as you try to port an idiom: a pattern built on passing a small unit around has no expression at all in a language whose unit cannot be passed, and it has to be rebuilt as a larger construct. ## Where this axis stops - **How a unit is parameterised over the types it works with** is a separate subject with its own mechanisms and trade-offs; on this axis you only note what the unit is. - **Design guidance** - which unit to prefer when several are available - is not what the axis records. The axis is a measurement of the language. - **File and package layout** is packaging, not composition. Code that cannot be passed does not become a composition unit because it lives in its own file. One line on the worksheet: name the unit, name the glue, note whether it is passable. That is the whole score, and it predicts more about how a codebase in that language will look than any paradigm label does.

  • A language lets you name a piece of behaviour but never pass it as an argument. What does that do to the score?
    It puts the unit at whatever larger construct *can* be passed. Naming alone gives you no substitution and no wrapping, so reuse has to be arranged by writing the choice into the call site or by building a larger passable thing to carry the behaviour. The glue becomes declaration, not passing.
  • Why does the unit of composition predict what the language's libraries look like?
    Because a library is written in the same currency its users spend. If the unit is small and passable, libraries ship many small pieces plus combinators to join them; if the unit is large, libraries ship a few large pieces you configure. The granularity of reuse is set by what can cross a boundary.

saying these in an interview costs you the question

  • Answers with file layout rather than what code can pass around
  • Assumes the unit of reuse is always a routine
  • Says a language has no unit of reuse without a class construct
  • Thinks something that cannot be passed still composes freely
  • Treats copy-and-paste reuse as evidence of a composition unit