A settings loader generic over its result must produce a default for a missing key — when is a caller-supplied factory better evidence than a token?
answer
- some types cannot be made from a name
- construction may need arguments or context
- evidence that builds, not just names
- hand over the making, not the type
- token still does conversion and checking
basics
~20 sA factory is evidence that can build: it covers types whose construction needs arguments or is not publicly reachable, and it keeps the loader out of reflective instantiation. A token only names a type, and building from one assumes a no-argument path exists.
solid answer
~50 sThe two are different kinds of evidence. A **class token** names a type, which is what a loader needs to pick a conversion and to check a produced value. Building from a token is a second, weaker thing: it works only where the named type has a reachable construction path taking no arguments, and it makes the loader reach for a reflective lookup that fails at run time rather than at the call. A **factory** — a function value the caller supplies — inverts that: the caller decides how the value is made, so types needing constructor arguments, an implementation choice, or captured context all work, and the failure to build is the caller's own code, reported at the call site's own terms. So take the factory for the *missing-key* path and keep the token for the *conversion and check* path; each does the job the other cannot.
code
pseudocode · 9 linesfunction readOrDefault(key, typeToken, makeDefault):
text = store.lookup(key)
if text is absent:
return makeDefault() # only this branch builds
value = converters.forType(typeToken).parse(text)
return typeToken.checkedNarrow(value) # token still checks the parsed path
endpoint = readOrDefault("api.endpoint", tokenFor(Endpoint),
function() { return Endpoint(host = "localhost", port = 8080) })go deeper
Remember the two shapes of the call: one hands over the type, the other hands over a small function that makes a value. The second exists because naming a type is not the same as being able to build one.
Explain what building from a name assumes — a reachable, argument-free, concrete construction path — and why the caller can satisfy all of that trivially while the loader cannot.
Show that you keep both pieces of evidence and tie them to one placeholder, call the factory only on the missing branch, and refuse defaults that can fail or that share a mutable object across callers.
The judgment is where construction knowledge lives. Centralising it in the loader gives one call shape and a run-time failure mode; pushing it to callers gives compile-time failures and more parameters. Pick one and make it the house style rather than mixing both.
## Two kinds of evidence, not two spellings of one When a type argument is discarded, callers hand evidence back in. The two common forms are easy to blur together and do genuinely different work: - A **class token** is evidence that *names*. It stands for one type, so the loader can look up a conversion for that type and can narrow a value against it. - A **factory** is evidence that *makes*. It is a function value the caller supplies, and calling it yields one value of the wanted type. It names nothing the loader can look up. Asking a token to make a value is where the confusion starts. Naming a type does not give you one; it gives you a name to ask a run-time facility for one, and that facility can only do the simplest case. ## Why building from a token is the weak case Creating a value from nothing but the name of its type assumes: 1. the type has a construction path that takes **no arguments** — anything needing a host, a size, a clock or a parent is out; 2. that path is **reachable** from where the loader stands, rather than hidden behind a builder or a private construction; 3. the type is **concrete** — an abstract shape or an interface names no single thing to build; 4. the lookup that finds the path can only report its failure **at run time**, which is the wrong end of the day for a configuration default. Every one of those is an assumption the caller could have simply satisfied, because the caller knows the type and is standing in code that can construct it. ## What the factory changes The factory moves the decision to the only place that has all the facts. The caller writes a one-expression function that builds the default, capturing whatever it needs, and the loader calls it on the branch where there is no text. The loader stops knowing anything about construction; it becomes a routine that either converts text or asks someone else for a value. That also fixes the reporting. If the default cannot be built, the failure is ordinary code at the call site, not a run-time complaint from inside a general-purpose lookup about a type name. | Job the loader needs done | A class token | A caller-supplied factory | |---|---|---| | Choose how to parse stored text | Yes — it names the target type | No — it produces values, it names no target | | Check a produced value | Yes — narrow against the named type | No — there is nothing to check against | | Produce a value when there is no text | Only where a no-argument path is reachable | Yes — the caller already decided how | | Produce a value needing arguments or context | No | Yes — they are captured in the function | | Report a build problem early | No — it surfaces when the read runs | Yes — it is the caller's own code | ## Where the token is still required This is the half candidates drop. Supplying a factory does not remove the need to name the type, because the *parse* path still has to select a conversion and still has to check what it produced. A read that takes a factory and no token has to fall back to guessing from the text, or to skipping the check. Real APIs of this shape take both, or infer the token from the factory's declared result at the call site where that type is still known. ## Using both without inventing a second truth Two pieces of evidence for one type is exactly the situation where they can disagree, so tie them in the signature: the token parameter and the factory's result are both declared as evidence for the same placeholder, and a call that mixes types does not compile. A few practical rules follow: - Call the factory **only on the missing branch**. A default that is expensive, or that opens something, should not run on every read that finds its key. - Return a **fresh** value per call unless the type is immutable; a factory that hands back one shared mutable object turns a default into a global. - Keep the factory total — a default that can fail is not a default, and its failure will surface during startup with no key attached. - Do not let the factory do the parsing too. Once it converts text it is a second conversion path, and the two will drift. ## The shape of the answer in an interview Say that a token is evidence that names and a factory is evidence that makes; that naming suffices for conversion and checking; that making is what construction needs, and that only the caller reliably can make. Then note that the two coexist in one signature and must be tied to the same placeholder.
- The loader takes both a token and a factory for the same placeholder. What stops a caller from passing a factory that builds a different type?The signature, if it declares both as evidence for the same placeholder: the token parameter is a token *of* that placeholder and the factory returns *that* placeholder, so a mixed call fails to compile. Left untied, nothing stops it, and the mismatch is only found when the missing branch first runs in production.
- Why call the factory lazily rather than taking the default value itself as a parameter?Because a plain value is built on every call, including the overwhelming majority that find their key. If the default opens a connection, allocates a large structure or reads a clock, that cost and those side effects happen for nothing. A function value defers the work to the one branch that needs it.
saying these in an interview costs you the question
- Assumes any named type can be built from its token alone
- Claims a factory makes the type argument visible at run time
- Drops the token once a factory is supplied, leaving the parse unchecked
- Treats token and factory as interchangeable evidence for both jobs
- Builds the default eagerly on every read, including hits