programming · level 3

Functions

Named, reusable behaviour.

100 XP

Functions

A function is a named, reusable chunk of behaviour. Every program above a few lines lives or dies by how its functions are factored.

The shape of a function

def greet(name):
    return f"Hello, {name}"

Three parts: the name (greet), the parameters (name), and the body (the indented block). Most languages also require an explicit return for non-void results. Python returns None if you don't.

Parameters vs arguments

Easy to confuse, important to keep straight:

  • Parameter — the name in the definition (name in def greet(name)).
  • Argument — the value passed at the call site ("Alice" in greet("Alice")).

A function definition declares parameters. A call passes arguments. Function signatures get described as "parameters"; bug reports say "I passed the wrong argument".

Return values

A function returns one value. To return multiple values, most languages let you wrap them in a tuple/array/object:

def divmod(a, b):
    return a // b, a % b      # returns a tuple

quotient, remainder = divmod(7, 2)
function divmod(a, b) {
  return [Math.floor(a / b), a % b];
}
const [quotient, remainder] = divmod(7, 2);

A function that returns nothing is sometimes called a procedure. Most languages treat it as returning a unit value (undefined, None, void).

Default arguments

When a parameter has a sensible default, give it one:

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}"

greet("Alice")              # Hello, Alice
greet("Alice", "Hi")        # Hi, Alice

The default is evaluated once when the function is defined, not on every call. A famous Python footgun:

def append(item, target=[]):
    target.append(item)
    return target

append(1)   # [1]
append(2)   # [1, 2] — the default list is shared between calls!

Use target=None and create the list inside the function instead.

Variable arguments

Most languages have a way to accept any number of arguments:

def sum_all(*nums):
    return sum(nums)

sum_all(1, 2, 3, 4)         # 10

def make_user(**fields):
    return User(**fields)

make_user(name="Alice", age=30)

JavaScript spelled it differently:

function sumAll(...nums) { return nums.reduce((a, b) => a + b, 0); }

Pure functions

A pure function is one whose output depends only on its arguments AND that has no observable side effects. Pure functions are easy to test, easy to reason about, and trivial to cache:

# Pure.
def square(x):
    return x * x

# Impure — depends on the wall clock.
def is_morning():
    return datetime.now().hour < 12

# Impure — mutates a parameter.
def normalize_in_place(items):
    for i, x in enumerate(items):
        items[i] = x.lower()

Most programs need impure functions (they have to read from disk, hit the network, write to a database). A useful design rule: push impurity to the edges and keep the core pure.

First-class functions

In most modern languages, functions are values. You can pass them as arguments, return them from other functions, store them in variables:

def apply_twice(f, x):
    return f(f(x))

apply_twice(lambda n: n + 1, 5)     # 7

This is the foundation of map, filter, reduce, and most of functional programming.

Naming functions

A function name should usually be a verb phrase that says what it does:

  • parse_date(s), send_email(user, subject), is_admin(user)
  • date(s), email(user), user_admin(user)

Boolean functions read better as is_x, has_x, should_x so the call site reads like English: if is_admin(user).