Chapter 3
Functions

In the previous chapter, we were introduced to how to write functions. We saw some simple ones that manipulated numbers, like square:

function square(x) {
  return x * x;
}

And we defined functions that produced images that we then used to construct animations. In this chapter, we are going to take a much deeper dive into functions, and focus on how we design functions in order to solve problems. On the way, we’ll explore programming in a more thorough way, and develop some concrete tools that we can use to approach problems.

Designing Functions

When we introduced functions in the last chapter, we described them (first) as a way to produce values that differed. But, there are other ways of thinking about them. Functions can also reduce duplication (as we saw in the example with drawing the frames of the bike motion). In this chapter we want discuss the process of actually creating them, as a creative design process.

Fundamentally, a function is a way of transforming one piece of information into another. In the example of square above, the first piece of information (or the input) was a number, and the second (or the output) was that number multiplied by itself. This relationship (between a number and it multiplied by itself) matches what we think of as squaring, so the name matches it well.

All programming is about similar transformations. And, it is mostly about the structure (ie, what it looks like, what it contains) of that information. Up until now, we have introduced different types of data that we can represent with javascript (like numbers, strings, images, etc) but haven’t talked much about how we choose what representation to use for a given task. It may seem, up until now, that there isn’t really any choice - if we are dealing with a number, we represent it as a number, and so on. But, we are usually writing programs about bigger ideas than just numbers or strings.

For example, let’s say we wanted to represent our friend "Austin" in our program. How should we do it? We could use the string "Austin", but later on, it’s possible that we would want to represent another "Austin" in our program, and that might be problematic. Or we might realize that we want to send an email to them, so maybe we should represent them by the string "austin@tlcjs.org", which is their (fictional, at least for now!) email address. An email address seems more unique, but what if "Austin" changes their email? Perhaps we should just represent "Austin", and any other people we have to deal with in our program, by (unique) numbers (for example, "Austin" could be 42), and if we need their email (or one of their emails) we can store that somewhere else, connected to 42, our unique number for "Austin".

Of course, we have the same problem in the opposite direction - if we have the number 42, what does it represent? It could be the number of members of TLC, it could be the age of someone, it could be someones favorite number, and any number of other things.

Figuring out what data exist in our program, and how it relates to the (real) problem we are trying to solve is a critical part of programming, and more concretely, designing functions, because all functions have input and output data.

Design Recipe

The first step in the Design Recipe (which is what this chapter is teaching) is to figure out what data you need to solve your problem. You should describe what information you have, and how you will represent it in the program. This should be in comments, as it is crucial information both for yourself and anyone else who will see this later.

As an example problem (which we’ll follow through the whole recipe), we want to figure out how a hypothetical bake sale is going to work.

We want to raise money by making lemonade and cookies (which we sell at 25c/cup and 50c/cookie), but unfortunately, the only place we have to sell them is run by a capitalist who makes us pay to use the space. They have two options we can choose between:

The function we’ll construct will take as input whether we want to pay the flat $50 fee, and how many cups of lemonade / cookies we sell. It’ll give back our total profit.

Thinking about the type of information that we need, and how we’ll represent it, we come up with:

// The number of cookies, glasses of lemonade are numbers.
// Whether we will pay the flat fee is a boolean (we either
// will or won't). We'll return a number in cents, as our
// prices are in cents.

For this problem, it may seem pretty trivial - but in many problems it isn’t always clear how to represent a given part of the problem (and not understanding is a good way to get lost!).

The second step is to figure out what the input and output to our function will be. In the first step, we should have figured out all the types of data that will appear, but we still have to define exactly what the signature of the function is, where a signature is a description of the type of all the inputs and the type of the output of a function.

You should also write this as a comment, as it is similarly very helpful information for yourself and others who might be looking at your program (or trying to change it). In our bake sale example, we end up with the following signature:

// number, number, boolean -> number

Where that is notation for a function taking three inputs: a number (cups of lemonade sold), another number (number of cookies sold), and a boolean (true means we pay the flat fee) and producing a number (our total profit) as output.

The third step is writing down the purpose of the function, in human language. Up until now, everything we have been writing down has been about the external attributes of the function - what it takes in and what it produces. There may be many different functions that have those inputs/outputs. This is the step where we describe, in the simplest possible way (but still, precisely), what the function should actually do. In our case, we might write:

// Takes the number of lemonades * 25 plus the number of
// cookies * 50 and then subtracts either 5000 (if the
// flat fee is chosen) or 10% of this total (if flat fee not
// chosen). This number, which is the number of cents, is
// what we return.

The fourth step is to write down the header for the function, which is the name, arguments, and some stub for the return value (where a stub is a value that matches the signature, but is as simple as you can think up). Part of this step involves coming up with names for the function and all the arguments. In this case, we might write:

function bakeSale(lemonades, cookies, useFixedCost) {
  return 0;
}

Note that for the stub, I could have easily written 2, or 1000 (which are all numbers). The point of the stub is not to have a sensible value, just to have something that matches the right type of data that your signature specified.

The fifth step is to write down example uses of your function, which are usually called "tests" (as they "test" that your function behaves properly).

In our case, we might write:

function bakeSale(lemonades, cookies, useFixedCost) {
  return 0;
}
shouldEqual(bakeSale(0,0,true), -5000);
shouldEqual(bakeSale(0,0,false), 0);
shouldEqual(bakeSale(200, 0, true), 0);
shouldEqual(bakeSale(200, 200, true), 10000);
shouldEqual(bakeSale(200, 200, false), 13500);

When writing tests, it is good to think both of the common cases (for example, selling a few hundred of each lemonade and cookies), but also the strange cases (like selling nothing at all, or all of one product).

When you run your program (which, at this point, you should), you’ll see a report of how many tests ran and passed (and if they failed, you’ll get a report of what went wrong). When you run them in files that you save, you’ll get the line number when they fail (but that’s not available within the in-text editors).

In this case, most of them failed (the ones that passed happened to work because we used 0 as our stub, so like a broken clock that is right twice a day, our always-returning-0 function is sometimes right!). This is expected! Since you haven’t written the body of the function yet, the tests should be failing. If they weren’t, they couldn’t be checking much!

The sixth step is to fill in the body of the function with a template for each of the arguments. A template is a bit of code that is how you would normally use the particular value. Each type of data has its own template. Of the types of data that we’ve seen so far:

The first three have a very simple template - it is just the identifier of the argument. The template for booleans is an if expression. So if we had a function:

// (1) Involves booleans.
// (2) Boolean -> Boolean
// (3) This function inverts a boolean value, turning true
//     to false and vice versa.
function not(b) { // (4)
  return true;
}
// (5)
shouldEqual(not(true), false);
shouldEqual(not(false), true);

Where I’ve identified each step of the design recipe completed thus far. To example the template for b (the only argument), we would write:

// (1) Involves booleans.
// (2) Boolean -> Boolean
// (3) This function inverts a boolean value, turning true to
//     false and vice versa.
function not(b) { // (4)
  if (b) {
    // ...
  } else {
    // ...
  }
  return true;
}
// (5)
shouldEqual(not(true), false);
shouldEqual(not(false), true);

The purpose of the template is to give you a place to start - if you have an argument that is a boolean, you are probably going to have an if somewhere. For our function, we have three arguments, so we have three templates to fill in. The first two are numbers, so the template is just the identifier, but the third one is a boolean, so it has a template similar to not above.

// The number of cookies, glasses of lemonade are numbers.
// Whether we will pay the flat fee is a boolean (we either
// will or won't). We'll return a number in cents, as our
// prices are in cents.
// number, number, boolean -> number
// Takes the number of lemonades * 25 plus the number of
// cookies * 50 and then subtracts either 5000 (if the flat
// fee is chosen) or 10%
// of this total (if flat fee not chosen). This number,
// which is the  number of cents, is what we return.
function bakeSale(lemonades, cookies, useFixedCost) {
  // ...
  lemonades;
  // ...
  cookies;
  // ...
  if (useFixedCost) {
    // ...
  } else {
    // ...
  }
  return 0;
}
shouldEqual(bakeSale(0, 0, true), -5000);
shouldEqual(bakeSale(0, 0, false), 0);
shouldEqual(bakeSale(200, 0, true), 0);
shouldEqual(bakeSale(200, 200, true), 10000);
shouldEqual(bakeSale(200, 200, false), 13500);

Now that we have all the parts on the page (so to speak), we can write the body. The templates are a guide, and can be removed if you end up not needing them (or using them in a different way), but they are often a useful way to start

Often, the best place to look for guidance of how to finish the program is either the purpose statement (which states, in english, what it should do), or the tests (which shows us some examples of what it has done). Using the purpose statement, we know that to calculate the total cents, we multiply the number of lemonades by 25 and the number of cookies by 50. That gets us further (and we have used up two of our templates):

// The number of cookies, glasses of lemonade are numbers.
// Whether we will pay the flat fee is a boolean (we either
// will or won't). We'll return a number in cents, as our
// prices are in cents.
// number, number, boolean -> number
// Takes the number of lemonades * 25 plus the number of
// cookies * 50 and then subtracts either 5000 (if the flat
// fee is chosen) or 10%
// of this total (if flat fee not chosen). This number,
// which is the  number of cents, is what we return.
function bakeSale(lemonades, cookies, useFixedCost) {
  var total = lemonades * 25 + cookies * 50;
  if (useFixedCost) {
    // ...
  } else {
    // ...
  }
  return 0;
}
shouldEqual(bakeSale(0,0,true), -5000);
shouldEqual(bakeSale(0,0,false), 0);
shouldEqual(bakeSale(200, 0, true), 0);
shouldEqual(bakeSale(200, 200, true), 10000);
shouldEqual(bakeSale(200, 200, false), 13500);

Now we want to use the useFixedCost template, so we have to think what happens if we want to use the fixed cost option (which is that we have to pay a fixed 5000 cents) and what happens if we don’t (which is that we lose 10%, or multiply the total by 0.9). This lets us complete the function:

// The number of cookies, glasses of lemonade are numbers.
// Whether we will pay the flat fee is a boolean (we either will or won't).
// We'll return a number in cents, as our prices are in cents.
// number, number, boolean -> number
// Takes the number of lemonades * 25 plus the number of cookies * 50
// and then subtracts either 5000 (if the flat fee is chosen) or 10%
// of this total (if flat fee not chosen). This number, which is the
// number of cents, is what we return.
function bakeSale(lemonades, cookies, useFixedCost) {
  var total = lemonades * 25 + cookies * 50;
  if (useFixedCost) {
    return total - 5000;
  } else {
    return total * 0.9;
  }
  return 0;
}
shouldEqual(bakeSale(0,0,true), -5000);
shouldEqual(bakeSale(0,0,false), 0);
shouldEqual(bakeSale(200, 0, true), 0);
shouldEqual(bakeSale(200, 200, true), 10000);
shouldEqual(bakeSale(200, 200, false), 13500);

As you are writing the function, you can continue to run your tests. Often, if you have many tests, some of them will start to pass as you write the function. In our case, all our tests are now passing!

Summary of Design Recipe

  1. Describe information in terms of data (define types)

  2. Signature (type of the function)

  3. Purpose Statement (what it does — in human language!)

  4. Function Header (function name, argument names, brackets, stub return value)

  5. Examples === Tests (run them, watch them fail)

  6. Mechanical Template (expand template for every argument based on type)

  7. Now you may write the body! (run tests until they pass!)

Exercises

// Involves booleans.
// Boolean -> Boolean
// This function inverts a boolean value, turning true to false and vice versa.
function not(b) {
  if (b) {
    // ...
  } else {
    // ...
  }
}
shouldEqual(not(true), false);
shouldEqual(not(false), true);
// A person's name as a string.
// String -> String
// This function will enthusiastically greet a person by name.
// If the name is blank, return "Hello!!"
function enthusiasticallyGreet(name) {
  if (false) { // replace false with test that name is empty string!
    // ...
  } else {
    // ...
  }
}
shouldEqual(enthusiasticallyGreet("Austin"), "Hello, Austin!!");
shouldEqual(enthusiasticallyGreet("Pea"), "Hello, Pea!!");
shouldEqual(enthusiasticallyGreet(""), "Hello!!");
// Involves numbers
// Number -> Number -> Number
// This function takes two numbers as input and return the
// one that is smaller.
function min(x, y) {
  //..
}
// Your tests here.

Using that function, can you use the design recipe to "center" a string within a larger space. For example, center(4, "hi") would return " hi ". Once you get a version working, you might work on figuring out what to do if the space specified is shorter than the string, or if the amount of space can’t be even on both sides.

// image -> number

For example, we can use this to get back the width of a rectangle we created:

print(width(rectangle(100,10,"red")));

Using this, write a function area that takes an image as input and returns the total number of pixels in the image.

// Data
// Type
// Function description.
function b52Equivalence(item, cost) {
  // ...
}
// Your tests here.