SkillGridLearn computer science by doing

Functions: a named box with one output

Arguments, return values, scope and side effects — and why a box the size of a sentence is the one you can actually test.

14 min · 45 XP

You have written the same three lines twice, in two places, with one number changed. Then you fix a bug in one copy and forget the other. A function exists to stop this. It is a named box: you put values in, one value comes out, and you write the contents once.

Think of it as a machine with slots. The inputs are called parameters when you define the box and arguments when you fill them in. The output is whatever comes back from return. Nothing else crosses the wall: the box cannot see the rest of your program except what you handed it.

        inputs            box             output
     price, count  -->  totalPrice  -->  a number

     price = 2.50, count = 4   -->   10.00Two inputs, one output. The wall in the middle is the point of the whole idea.

The same box in three languages: price times count.

def total_price(price, count):
    return price * count

total = total_price(2.50, 4)Python: def, a colon, and the indented body.
function totalPrice(price, count) {
  return price * count;
}

const total = totalPrice(2.50, 4);JavaScript: function, braces, and return with a semicolon.
func totalPrice(price float64, count int) float64 {
	return price * float64(count)
}

total := totalPrice(2.50, 4)Go: func, the type of every input, and the type of the output after the closing parenthesis.

Arguments are matched to parameters by position, not by meaning. The language has no idea that the first parameter was called price; it only knows that the first argument goes into the first slot.

total_price(2.50, 4)      arguments in the call
2.50  ->  price           parameters in the definition
4     ->  count

total_price(4, 2.50)      same shape, different meaningThe same two numbers, swapped. Both lines run. Only one is correct.

Small functions are easy to test, which is the practical reason to write them. In dev.testing you saw that a test is a claim about what the code does. A function with two inputs and one output makes that claim trivial to write, because there is nothing else to set up: no database, no browser, no order of events.

def discount(total):
    if total >= 20:
        return total * 0.9
    return total

assert discount(20) == 18.0
assert discount(10) == 10.0Two lines describe the whole function. No setup, no state, no surprises.

So the rule is not aesthetic. A function that takes forty lines and reads three globals cannot be tested without building half the world around it, so nobody tests it, so it breaks. A box the size of a sentence can be checked in one line and fixed in one minute.

maintotalPriceresultprice = 2.50, count = 4price * count10.00total = 10.00main keeps running, the box is gone

Scope is the answer to what a name can see. When you call a function, it gets its own little room. Names created inside that room exist only while the call runs, and disappear when return hands the answer back. The function can read names from outside, but the outside cannot see inside.

const rate = 0.21;

function vat(net) {
  const inner = net * rate;
  return inner;
}

vat(100)
innerrate is visible inside the function. inner is not visible outside it.

Finally, an honest word about side effects. A pure function only looks at its arguments and only produces its return value. Call it with the same arguments a thousand times and you get the same answer a thousand times. An impure function also changes something outside itself: it prints, it writes a file, it sends a request, it updates a counter.

pure:      def vat(net): return net * 0.21
           vat(100) always 21.0

impure:    def vat(net):
               print("calculating")
               total = net * 0.21
               save_to_file(total)
               return totalThe first one you can predict. The second one you can only run and hope.

Practice

  1. A function is supposed to give you the price with tax. It prints the right number, but the caller receives nothing back. What is the mistake?

    Hints
    • A box returns a value; printing a value and returning one are two different actions.
    • Ask what the caller receives. If the function shows the answer on screen and hands nothing back, the caller gets nothing.
  2. Match each function with what it does to the world outside itself.

    Hints
    • A pure function is a box you can predict from its inputs alone.
    • Side effects are not evil: a program that never writes or prints anything does nothing useful. The point is to notice them.
  3. Put the moments of one function call in order, from the call to the line after it.

    1. 1The call starts and the arguments land in the parameter slots
    2. 2The body runs and reads its own names
    3. 3return hands one value back to the caller
    4. 4The caller works out the argument values
    5. 5The function stops and the caller continues with the next line
    Hints
    • The call cannot do its work before the arguments have a value.
    • Names inside the box are created when the call starts and disappear when return hands the answer back.