Functional Programming for Babies

With apologies to Chris Ferrie

A Function

This is a function.

(num: number) => num + 3;

A function is like a mirror. When you look at something through a function, it lets you see it differently.

Reverse Function

Here we see what the string jabberwocky looks like through the reverse function.

reverse("jabberwocky"); // "ykcowrebbaj"

When we see something through a function, we could say that’s what it reflects, but usually we say that’s what it returns.

Weird Return Values

Sometimes things look really different when viewed through a function, like when a mirror is colored and bent and cloudy.

getUserPosts("bob"); // [{date: 2022-11-25, likes: 3 ...}, ...]

It’s hard to see how "bob" could ever look like that, but that’s the fault of getUserPosts, not "bob".

Functions as Return Values

It would take a special sort of mirror to make something reflected in it look like a mirror itself. But some functions can return functions. Here is a function that combines an old function with a logging function, so you can record all the things the original function does.

(logger: Function, fn: Function) => {
  const logged = (...args) => {
    const res = fn(...args);
    logger(res);
    return res;
  };
  return logged;
};

Curried Functions

For that last function to work, it wants two things at once, not just one. It will throw a tantrum if you give it just one thing. But somebody figured out how to teach functions to be patient, so if they don’t have all the things they want right away, then instead of throwing a tantrum they just return another function that will wait nicely for the rest of the things.

const addLogger = (logger: Function) => (fn: Function) => {
    ...
}
const addMyLogger = makeLogger(myLogger);
const myFuncLogged = useMyLogger(myFunc);

This is called currying, after Haskell Curry, because people like to name things after him.

Function Composition

If you keep looking at the same thing through the same function, it will keep looking the same.

reverse("jabberwocky"); // "ykcowrebbaj"
playChess();
reverse("jabberwocky"); // "ykcowrebbaj"
move("a7", "a8");
reverse("jabberwocky"); // "ykcowrebbaj"

You can use what a function returns just as well as you can use the original thing. That’s called referential transparency, and means you can string a bunch of functions together, just like you can line up a bunch of mirrors or lenses.

countLikes(getUserPosts(db)("bob")); // 36

When you do this, it’s called function composition.

Compose Function

You can use a special helper function called compose to do this:

const getUserLikes = compose(countLikes, getUserPosts(db));
getUserLikes("bob"); // 36

Impure Functions

Some functions are dangerous. Instead of just taking whatever they are shown and giving a return value, they do other things, too.

Mutating Data

What if you held something up in a mirror, and the mirror broke it, or set it on fire, or turned it into a cat? This function is like that.

(data: Object) => {
  Object.keys(data).forEach((key) => (data[key] = "cat"));
  return data;
};

Perhaps somebody loved data once. The values of data may have had rich, long histories and given meaning to the lives of others. But now they are all just "cat".

Different Return Values

What if you kept looking in a mirror, and kept seeing different things? This function is like that.

(data: Object) => Object.values(data).map((value) => value + Math.random());

Perhaps data was painstakingly calculated to give deep meaning to all the functions it touched. But instead this function just threw noise at it.

Random Other Stuff

What if a mirror looked like a nice, ordinary function, but did something else when you weren’t looking? This function does that.

(data: Object) => {
  kickPuppies();
  return Object.values(data).map((value) => doStuffWith(value));
};

Here data fulfills its mission, but at what cost! Puppies have been kicked for no reason at all.

Pure vs Impure

In a way, these are not real functions at all, but people call them functions. We in turn call them impure functions, since they’re almost like the good, true, pure functions, but they’re poisoned by doing things no real function would ever dream of: they have side effects.

These side effects promise great power, but they must be carefully controlled, or they will corrupt their function and bring about untold chaos and suffering.

Windowless Monads

But we need the power of side effects, if only for getting information in and out of the program; otherwise it is just a windowless monad.

There are some ways around this, but I’ll write about them later.