30 Seconds of Code is a brilliant collection of JavaScript snippets, digestible in ≤ 30 seconds. Anyone looking to master JavaScript should go through the entire thing.

Inspired by Ramda, I contributed when() to 30secondsofcode’s official GitHub repo. This is one my favorite functions.

when() takes 3 parameters:

  1. pred: A predicate function (must return true or false)
  2. whenTrue: A function to run if pred returns true.
  3. A value: x.

Here’s the most basic implementation:

when = (pred, whenTrue, x) => {
  if (pred(x)) {
    return whenTrue(x);
  } else {
    return x;
  }
};

Which you can shorten to:

when = (pred, whenTrue, x) => (pred(x) ? whenTrue(x) : x);

Let’s say we want to triple even numbers

when((x) => x % 2 === 0, (x) => x * 3, 2);
// 6

We got 6 because 2 is an even number. What if we pass 11?

when((x) => x % 2 === 0, (x) => x * 3, 11);
// 11

A Step Further

when currently needs all 3 parameters at once–what if we could supply just the first 2, and give x later on?

when = (pred, whenTrue) => (x) => (pred(x) ? whenTrue(x) : x);

This version’s what I submitted to 30secondsofcode.org. Now our code’s more flexible.

tripleEvenNums = when((x) => x % 2 === 0, (x) => x * 3);

tripleEvenNums(20); // 60
tripleEvenNums(21); // 21
tripleEvenNums(22); // 66

Even Further Beyond

We can pass x later because when(pred, whenTrue) returns a function expecting x. What if we curry when()?

If you’re new to currying see my article on it.

A curried function doesn’t need all its parameters at once. You can supply some and get a function that takes the rest, allowing for powerful patterns.

A Silly Example

Imagine we have two lists of people, both contain a guy named Bobo.

Bobo wants a nickname for each list.

  • If we find Bobo in list 1, change his name to B Money.
  • If we find Bobo in list 2, change his name to Bo-bob.

Currying when allows us to easily write a function for each concern.

If you’re following along, here’s a curry function from 30secondsofcode.org.

curry = (fn, arity = fn.length, ...args) =>
  arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);

We’ll need a predicate to find Bobo.

isBobo = (person) => person.name === 'Bobo';

To keep our functions pure, we’ll need a way to immutably change a person’s name.

changeName = (newName, obj) => ({
  ...obj,
  name: newName
});

Let’s also curry it so we can supply just newName.

changeName = curry((newName, obj) => ({
  ...obj,
  name: newName
}));

Here’s our lists.

list1 = [
  {
    name: 'Bobo',
    id: 1,
    iq: 9001
  },
  {
    name: 'Jaime',
    id: 2,
    iq: 9000
  },
  {
    name: 'Derek',
    id: 3,
    iq: 8999
  }
];

list2 = [
  {
    name: 'Sam',
    id: 1,
    iq: 600
  },
  {
    name: 'Bobo',
    id: 2,
    iq: 9001
  },
  {
    name: 'Peter',
    id: 3,
    iq: 8
  }
];

Let’s map over list1.

doIfBobo = when(isBobo);
renameToBMoney = changeName('B Money');

list1.map(doIfBobo(renameToBMoney));

Our result:

[
  {
    name: 'B Money',
    id: 1,
    iq: 9001
  },
  {
    name: 'Jaime',
    id: 2,
    iq: 9000
  },
  {
    name: 'Derek',
    id: 3,
    iq: 8999
  }
];

Because of when, we only changed Bobo and ignored everyone else!

Now map over list2.

renameToBoBob = changeName('Bo-bob');

list2.map(doIfBobo(renameToBoBob));
Our result:

[{
  "name": "Sam",
  "id": 1,
  "iq": 600
},
 {
 "name": "Bo-bob",
   "id": 2,
   "iq": 9001**
 },
 {
   "name": "Peter",
   "id": 3,
   "iq": 8
 }
];

Looks good to me! We gave Bobo his nicknames without affecting anyone else.

If you’re further interested, consider these links: