As a lead, how would you decide whether a codebase may use text-level substitution when a tree-rewriting facility exists?
answer
- ask first whether expansion is warranted
- cheap to write, expensive to read
- one expression, fully parenthesised
- refusal and control flow need structure
- a rewrite is a program needing maintainers
basics
~20 sDecide on review cost, not power. Text-level substitution is acceptable only where the body is one fully parenthesised expression; anything contributing statements, or needing to refuse bad input, belongs to a tree rewrite or an ordinary function.
solid answer
~50 sStart by asking whether either is warranted: most shorthands exist to avoid typing, and a plain function or a named value costs a reader nothing. Where expansion genuinely is the point, the choice is about who pays. A text-level paste is cheap to write and puts the cost on every future reader, who must expand it mentally to know what a call site parses as, and on whoever debugs a diagnostic that lands in generated characters. A tree rewrite is more to build and to maintain — it is code written against the language's own structures, with tests of its own — and it removes a whole family of accidental failures. So I would permit text-level substitution only under narrow written rules: one expression, every parameter use and the body parenthesised, never a statement sequence, a named owner, and a test that pins the expansion. Everything else goes to the tree level.
go deeper
Recall that a shorthand is not free: someone reading the call has to know what it expands to, which is a reason to prefer an ordinary function whenever one would work.
Name the concrete rules that make text-level substitution safe — one expression, fully parenthesised, never a statement sequence — and say which failure each rule prevents.
Argue the trade in terms of cost transfer: who pays to write it, who pays to read it, and who pays when a diagnostic lands in code nobody typed.
Set the standard and its escape hatches, decide what is mechanised versus reviewed, and name the signals that would make you revisit the call later.
## Ask first whether either is warranted The decision that saves the most is the one taken before the two options are compared. Most shorthands in a codebase are not there because expansion is required; they are there because someone did not want to repeat three tokens. Before choosing a level, rule out the alternatives that cost a reader nothing: - An ordinary function, where the only goal was to name a computation. - A named constant or value, where the only goal was to name a literal. - A small type, where the shorthand was standing in for structure. Expansion earns its place only when the thing being abstracted is not a value — when it must run in the caller's own scope, capture the caller's control flow, or produce code that a call could not. ## What the two levels actually differ on | Dimension | Text-level substitution | Tree-level rewrite | |---|---|---| | Cost to write | minutes | a program with its own tests | | Cost to read | every reader mentally expands the call | the call reads as a call | | Failure mode | silent regrouping, statements escaping a branch | a wrong tree, which is at least a bug in code you can test | | Diagnostics | land inside generated characters | can land on the caller, if positions are carried | | Ability to refuse bad input | effectively none beyond counting arguments | arity, kind, well-formedness | | Who can maintain it | anyone | whoever knows the language's tree shapes | Read that table as a transfer of cost. Text-level substitution moves work from the author to every future reader and to whoever is on call when the expansion misbehaves. A tree rewrite front-loads the work onto the author and concentrates the maintenance burden on a smaller group. ## A rule set that makes text-level substitution survivable Where the toolchain offers only the text level, or where the rewrite is genuinely not worth building, I would write the rules down rather than hope for discipline: 1. **One expression, never a statement sequence.** This removes the entire class where a branch keeps only the first statement. 2. **Every parameter use parenthesised, and the whole body parenthesised.** This closes both precedence boundaries. 3. **A named owner and a fixed, small set.** A shorthand nobody owns is a shorthand nobody will delete. 4. **A test that pins the expansion at a call site that puts the result next to a tighter operator.** That is the call site that would otherwise find the bug in production. 5. **No shorthand introduced to save typing.** If a function would do, a function does. Rules 1 and 2 are the ones that actually pay; rules 3 to 5 are what keeps the set from growing. ## When to spend the tree rewrite - The expansion must **refuse** bad input with a message pointing at the caller. - The expansion produces **more than one statement**, or interacts with control flow. - The shorthand is **used widely enough** that a reader-side cost multiplied across the codebase exceeds the one-time authoring cost. - The result is **generated for consumption by people who did not write it**, where a bad diagnostic is the whole experience. Against that, be honest about the bill: a rewrite is a program that produces programs, it needs its own tests, it is written against structures that change, and it narrows the group of people who can safely touch it. A team that builds one and then cannot staff it has bought a different problem. ## How to know later that the call was wrong A standard is only as good as the signals that make you revisit it: - Reviewers start asking "what does this expand to?" in pull requests — the reader-side cost has become visible. - Build failures point at positions no one can find in the source. - The blessed set of shorthands grows past what one page can list. - A shorthand acquires a second form "for the statement case" — the rule that kept it to one expression has already broken. The defensible position is rarely "ban one level" or "always use the other". It is that expansion of any kind is a cost paid by readers, that the cheap level pays for itself only under rules nobody is allowed to bend, and that the moment a shorthand needs to refuse input or carry control flow, it has outgrown pasting characters.
- A team argues the text-level rules are enough because reviewers enforce them. What would you say?That an unenforced rule is a statistic. Review catches most violations and not the one written under deadline, and the failures this rule prevents are silent — a regrouped expression or a statement outside a branch produces a wrong answer, not a build error. Either mechanise the check or accept that the set must stay small enough to read in one sitting.
- What would make you keep an existing text-level shorthand rather than convert it?A one-expression body that is already fully parenthesised, a small number of call sites, no need to reject bad input, and no involvement with control flow. Conversion has a cost of its own, and replacing a compliant paste with a program to maintain buys nothing but risk.
- How do you keep a tree rewrite from becoming a single-maintainer asset?Treat it as production code: tests over its output for the shapes that matter, a written note on what it refuses and why, diagnostics good enough that a caller never has to read the rewrite, and at least two people who have changed it. If none of that is affordable, that is an argument against building it at all.
saying these in an interview costs you the question
- Argues the tree level is always correct regardless of what is being abstracted
- Treats the choice as a language-power question rather than a maintenance one
- Assumes a written convention is equivalent to an enforced check
- Ignores that expansion itself costs every future reader something
- Reaches for expansion where an ordinary function would do