skip to content

In an editor that keeps each shortcut's behaviour in a map entry, what does that require of functions?

level: juniorimportance: must knowfreq 66%

answer

  1. behaviour is data here
  2. anywhere a number can go
  3. variable, field, map entry, argument
  4. no central conditional over codes
  5. the table itself dispatches

basics

~20 s

Functions have to be ordinary values: storable in a variable, a field or a map entry, and passable and returnable like a number. The table then holds behaviour itself rather than a code that some conditional must interpret.

solid answer

~40 s

A function value is a value in the full sense - it can be bound to a variable, held in a field, put in a map entry, handed to another routine as an argument, and handed back as a result. That is what lets the shortcut table *be* the dispatcher: the entry for a key carries the behaviour, so adding a shortcut is one new entry and nothing else changes. Without that property you store a code or a name in the table and keep a central conditional that turns each code into a call, so every new action edits a second place. Both a named declaration and an inline literal produce the same kind of value, and the entry cannot tell them apart.

code

pseudocode · 10 lines
pseudocode
table = empty map
table["ctrl+s"] = save                          // a named declaration
table["ctrl+q"] = function() prompt("quit?") end // an inline literal

function lookup(t, k)
    return t[k]        // returns a function value, like returning a number
end

action = lookup(table, "ctrl+s")
if action is missing then report unbound key else action()

go deeper

for a junior

Say plainly that a function can live in a variable, a field or a map entry and be handed around like a number, then show the entry being looked up and applied.

for a middle

Explain what the alternative costs: an action code per entry plus one conditional that every new action must edit, against a table whose entries carry the behaviour directly.

for a senior

Treat the table as an extension point and talk about duplicate keys, registration order, and what a missing entry should do at press time rather than at registration.

for a principal

Weigh a bare function value per entry against a record with a behaviour field, once entries also need a label, an enabled test and an undo step - that is a design decision, not a syntax preference.

## What first-class actually asks of a language Calling functions **first-class** is a claim about where a function is allowed to appear. A value is first-class when the language lets it go everywhere its ordinary values go. For functions that means, concretely: - bound to a local variable or a constant; - stored in a field of a record or an object; - placed in a collection - an element of a list, a value in a map, a member of a set; - passed as an argument to another routine; - returned as the result of a routine; - created anonymously at the point of use, without a declaration statement. Nothing in that list is exotic; it is the same list you would write for an integer. The interview-worthy part is what the property enables, not the definition itself. ## The table with behaviour versus the table with codes An editor needs each keyboard shortcut to trigger some behaviour. There are two shapes for that. | design | what the entry holds | where dispatch lives | cost of a new action | |---|---|---|---| | behaviour as data | a function value | in the table itself | one new entry | | codes plus a conditional | an action code or a name | in one central conditional | a new entry **and** a new branch | The second shape is what you are forced into when behaviour is not a value. It works, and plenty of systems ship it, but the dispatch decision is split across two places that must stay in step. The first shape collapses them: the lookup **is** the decision, because what comes back is already the thing to run. ## Where such a value may live The same value can be reached three ways in one program, and it is worth being able to show all three: 1. A **variable** holds it while the code decides what to register. 2. A **field** on a record holds it alongside a label and an enabled flag, so a command carries its behaviour with its metadata. 3. A **map entry** holds it under the key that should trigger it. In all three the value is the same kind of thing. Passing it into a helper that registers it, or returning it from a lookup, changes nothing about it - a function value survives being moved around exactly as a number does. ## Passing and returning the value A routine that takes the table and a key and returns the stored entry is returning a function value, and the caller then applies it. Notice how little ceremony that needs: no wrapper type, no interpretation step, no agreement about what the code `7` means. The behaviour travelled as itself. That is also why a missing entry has to be handled explicitly - the absence of behaviour is a real case, and it is one the code-plus-conditional shape usually handles with a default branch instead. ## What the design buys and what it costs The gains are concrete: - New actions are additive; no existing site enumerates the set of actions. - The table reads as a specification of the key map, one line per binding. - The behaviour can be applied by a test directly, without simulating a key press. The costs are real too: - A table of function values is harder to print, serialise or show in a settings screen than a table of codes, because a function value usually carries no useful description of itself. - If entries later need a label, an undo step or an enabled test, the entry has to become a record with a behaviour field rather than a bare function value - a small refactor, but one worth anticipating. - Some languages will happily accept a value of the wrong kind in the entry, so the mistake of storing a result rather than a function is caught late. ## What this property is not Being able to hold and move a function value is the base property. It is not the same as writing routines that consume or produce other functions as a design technique, it is not the capture of surrounding variables by a function written inside another, and it is not the type notation used to describe the entry. Those are separate subjects that all rest on this one. The claim here is narrow and worth stating precisely: behaviour can be a value, so behaviour can be data, so a lookup table can dispatch.

  • What does the same shortcut table look like in a language where behaviour is not a value?
    Entries hold action codes or names, and a central conditional maps each code to a call. The table then carries only half the dispatch decision, and every new action edits that conditional as well as the table.
  • What does the dispatcher gain when a new shortcut is added?
    Nothing changes in it at all. The entry carries the behaviour, so registration is purely additive and no site enumerates the set of actions. Only the missing-entry case still lives in the dispatcher.

saying these in an interview costs you the question

  • Says only dynamically typed languages can put a function in a map
  • Thinks storing behaviour always needs a one-method wrapper object
  • Confuses storing the function with storing its name as text
  • Believes a stored function keeps running in the background
  • Thinks the whole table must be rebuilt to add one action