The Power of Names
"To know the true name of a thing," wrote Ursula K. Le Guin in A Wizard of Earthsea, "is to have power over it." In her world of magic, wizards who learn the true name of wind can command the wind. Those who know the true name of a stone can shape it.
Programming shares this ancient intuition. When we name a transformation, we gain power over it. We can invoke it. We can share it. We can build upon it.
What Is a Function?
Recall our chess move from the previous chapters. We described a transformation: given a board state and a move, produce a new board state.
We could write this transformation every time we need it. But that would be tedious, error-prone, and---most importantly---it would hide the meaning of what we're doing.
Instead, we give the transformation a name:
Now, whenever we want to apply a move, we simply invoke the name:
state_1 = apply_move(state_0, knight g1 → f3)
state_2 = apply_move(state_1, pawn d7 → d5)
state_3 = apply_move(state_2, knight b1 → c3) The name apply_move encapsulates all the complexity we discussed before---the differences, the derivation, the transformation. We don't need to think about those details every time. We just use the name.
The Anatomy of a Name
A function has three parts:
- A name: How we refer to it (
apply_move) - Inputs (also called parameters): What it needs (
state,move) - Output (also called return value): What it produces (the new state)
Think of a function as a box---a machine with slots on one side for inputs, and a slot on the other side where the output emerges:
A simpler example---adding two numbers:
function apply_move(state, move):
^^^^^^^^^^ ^^^^^^^^^^^^
name inputs
... transformation logic ...
return new_state
^^^^^^^^^
outputThe inputs are like questions we ask. The output is the answer. The function's body---the transformation inside the box---is the process of figuring out the answer. But users of the function don't need to see inside. They only need to know: the name, what to give it, and what they'll get back.
Naming as Abstraction
When we name something, we create a layer of abstraction. The name stands for the thing, but is not the thing itself. It's a handle, a reference, a way of pointing.
Consider our chess example. We might build up layers of names:
function is_valid_move(state, move):
... check if the move follows chess rules ...
function apply_move(state, move):
... produce the new state ...
function play_game(initial_state, moves):
current = initial_state
for each move in moves:
if is_valid_move(current, move):
current = apply_move(current, move)
return current Notice how play_game doesn't know how apply_move works. It doesn't care about the details of board representation, piece movement, or state derivation. It only knows the name and the contract: give it a state and a move, get back a new state.
This is the power of naming. We can think at higher levels. We can delegate details to lower levels. We can build complexity from simplicity.
Names Give Us Vocabulary
When we name the concepts in our domain, we create a vocabulary---a language for talking about and manipulating that domain.
For chess, our vocabulary might include:
apply_move--- make a moveis_valid_move--- check if a move is legalis_check--- is the king under attack?is_checkmate--- is the game over?get_legal_moves--- what moves are possible?
Each name captures a concept. Together, they form a language. With this language, we can express complex ideas concisely:
if is_checkmate(state):
winner = opponent(state.turn)
else if is_check(state):
legal = get_legal_moves(state)
... must escape check ...Compare this to manipulating raw board arrays and piece positions directly. The named version is:
- Easier to read
- Easier to verify (does it match our understanding of chess?)
- Easier to change (if checkmate rules change, update one function)
The Dual Nature of Names
Names serve two masters: humans and machines.
For machines, a name is just a label---a way to find the associated code and execute it. The machine doesn't care if we call it apply_move or f17 or xyzzy.
For humans, names carry meaning. A name like apply_move tells us what the function does. A name like f17 tells us nothing. A name like calculate_result is vague---calculate what? From what?
// Bad: what does this do?
x = f(a, b)
// Better: intention is clear
new_state = apply_move(current_state, player_move)The best names are:
- Descriptive: They tell you what the function does
- Consistent: Similar things have similar names
- Honest: They don't lie or mislead
A Note on Purity
The functions we've discussed so far are pure. They take inputs and produce outputs. They don't change anything in the outside world. They don't depend on anything except their inputs.
This purity makes names especially powerful. When we see:
result = some_function(x, y)We know that:
resultdepends only onxandyxandyare unchanged- Calling it again with the same inputs gives the same output
The name becomes a reliable promise. We can trust it completely.
We have seen that naming transformations gives us power---the power to invoke, compose, and build abstractions. But so far, our functions are static things. We define them, we call them.
In the next chapter, we discover something stranger: functions themselves can be treated as values---things we can store, pass around, and return. Verbs become nouns.