Verbs Become Nouns
In human language, verbs describe actions and nouns describe things. "Run" is a verb. "Runner" is a noun. We can transform one into the other: "to run" becomes "the act of running."
Programming has the same duality. Functions are verbs---they describe actions, transformations, processes. But in many languages, functions can also be treated as nouns---as things that can be stored, passed around, and manipulated.
This chapter explores that transformation: when verbs become nouns.
Functions as Values
Consider a simple function:
function double(x):
return x * 2 We can call this function: double(5) returns 10.
But we can also refer to the function itself, without calling it:
my_function = double // not double(...) Here, my_function now holds the function double---not the result of calling it, but the function itself. We can call it later:
my_function = double
result = my_function(5) // result is 10The function has become a value. A verb has become a noun.
Why Does This Matter?
This might seem like a curiosity. Why would we want to store a function in a variable?
The power emerges when we realize we can:
- Choose which function to use at runtime
- Pass functions to other functions
- Return functions from functions
Let's explore each.
Choosing at Runtime
Imagine our chess game has different AI opponents:
function random_move(state):
return pick_random(get_legal_moves(state))
function greedy_move(state):
return pick_best_capture(get_legal_moves(state))
function defensive_move(state):
return pick_safest(get_legal_moves(state))Notice something: each strategy is built by chaining functions together. The output of one becomes the input of the next. And the whole chain is itself a function:
A player might choose their opponent's style:
if difficulty == "easy":
opponent_strategy = random_move
else if difficulty == "medium":
opponent_strategy = greedy_move
else:
opponent_strategy = defensive_move
// Later, in the game loop:
opponent_move = opponent_strategy(current_state) The variable opponent_strategy holds a function. Which function depends on the player's choice. The rest of the code just calls opponent_strategy---it doesn't need to know which specific function it is.
Passing Functions
We can pass functions as arguments to other functions. This is called using a callback---we're saying "call this function back when you need it."
function play_game(initial_state, white_player, black_player):
state = initial_state
while not is_game_over(state):
if state.turn == white:
move = white_player(state)
else:
move = black_player(state)
state = apply_move(state, move)
return stateNow we can play any combination:
// Human vs AI
play_game(start, human_input, greedy_move)
// AI vs AI
play_game(start, random_move, defensive_move)
// Human vs Human
play_game(start, human_input, human_input) The play_game function is parameterized by behavior. It doesn't just take data---it takes functions that determine how decisions are made.
Returning Functions
Functions can also return other functions. This creates functions dynamically.
function make_multiplier(factor):
function multiply(x):
return x * factor
return multiply double = make_multiplier(2)
triple = make_multiplier(3)
double(5) // returns 10
triple(5) // returns 15 What does double look like inside? It's a closure---a function paired with captured state:
We've created a function factory---a function that produces other functions. Each produced function has its own "personality" determined by the factor.
Functions Remember: Closures
In the make_multiplier example, something subtle happened. The inner function multiply refers to factor---but factor is a parameter of the outer function.
When we call make_multiplier(2), the value 2 is bound to factor. The returned function "remembers" this value, even after make_multiplier has finished executing.
This is a closure: a function bundled together with references to its surrounding state.
function make_counter():
count = 0
function increment():
count = count + 1
return count
return increment
counter_a = make_counter()
counter_b = make_counter()
counter_a() // returns 1
counter_a() // returns 2
counter_b() // returns 1 (separate count!) Each call to make_counter creates a new, independent counter. The closures carry their own private state.
Anonymous Functions
Sometimes we need a small function just once, for a specific purpose. Naming it seems like overkill.
Many languages allow anonymous functions---functions without names:
// Named function
function double(x):
return x * 2
// Anonymous function (same thing, no name)
(x) => x * 2We can use anonymous functions directly:
numbers = [1, 2, 3, 4, 5]
doubled = map(numbers, (x) => x * 2)
// doubled is [2, 4, 6, 8, 10] The anonymous function (x) => x * 2 is created just for this one use. It has no name---it's a "verb" used immediately, not stored as a "noun."
The Strange Loop
There's something almost paradoxical about functions being values. A function is an action, a process, a transformation. Yet we can hold it still, examine it, pass it around like an object.
It's like being able to hold "the act of running" in your hand. Not a runner, not the result of running---but running itself, frozen as a thing.
This creates opportunities for self-reference. A function can:
function repeat(action, times):
if times == 0:
return "done"
else:
action()
return repeat(action, times - 1)- Return itself
- Receive a copy of itself as an argument
- Create modified versions of itself
Here, repeat receives a function (action) and calls itself with modified arguments. The function processes functions and refers to itself.
Notice something profound: this code isn't just a program. It's a definition of what repeating means. We could express the same idea in natural language:
To repeat something zero times is to do nothing. To repeat something $n$ times is to do it once, then repeat it $n-1$ times.
This is abstract logic thinking. The definition works regardless of what the action is. It captures the pure essence of repetition---the pattern itself, separated from any specific instance.
Tracing Execution Through Substitution
Let's trace what happens when we call repeat(echo("hello"), 4). Rather than "executing" in the procedural sense, we can think of each step as substitution---replacing a function call with its equivalent form:
repeat(echo("hello"), 4)
= echo("hello"); repeat(echo("hello"), 3)
= echo("hello"); echo("hello"); repeat(echo("hello"), 2)
= echo("hello"); echo("hello"); echo("hello"); repeat(echo("hello"), 1)
= echo("hello"); echo("hello"); echo("hello"); echo("hello"); repeat(echo("hello"), 0)
= echo("hello"); echo("hello"); echo("hello"); echo("hello"); "done"
-> "hello", "hello", "hello", "hello"This resembles algebraic manipulation in mathematics! We aren't "running" anything---we're rewriting the program into a more verbose but equivalent form.
Functions as values opens a door to a new style of programming. Instead of telling the computer how to do something step by step, we can describe what we want by combining functions.
In the next chapter, we explore this further: functions that receive other functions as arguments, allowing us to delegate the "how" while focusing on the "what."