JavaScript/Higher-order programming/Example 1

From Spherical
Revision as of 00:43, 15 June 2013 by Apollolux (talk | contribs) (created)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

We have HP, and we want some way to heal ourselves:

var hero = {hp: 10, hpMax: 85};

It'd be nice to have a Potion, a Super Potion and a Mega Potion, healing 10, 20 and 50 HP respectively. It'd also be nice to see a dialog saying which item was used and how much HP we recovered.

The chump's way

function Potion() {
  var oldHp = hero.hp;
  hero.hp += 10;
  if (hero.hp > hero.hpMax) hero.hp = hero.hpMax;
 
  var heal = hero.hp - oldHp;
  if (heal > 0)
    Print.printLine("Drank Potion! Recovered " + heal + " HP.");
  else
    Print.printLine("Drank Potion! Nothing happened.");
}
 
function SuperPotion() {
  var oldHp = hero.hp;
  hero.hp += 20;
  if (hero.hp > hero.hpMax) hero.hp = hero.hpMax;
 
  var heal = hero.hp - oldHp;
  if (heal > 0)
    Print.printLine("Drank Super Potion! Recovered " + heal + " HP.");
  else
    Print.printLine("Drank Super Potion! Nothing happened.");
}
 
function MegaPotion() {
  var oldHp = hero.hp;
  hero.hp += 50;
  if (hero.hp > hero.hpMax) hero.hp = hero.hpMax;
 
  var heal = hero.hp - oldHp;
  if (heal > 0)
    Print.printLine("Drank Mega Potion! Recovered " + heal + " HP.");
  else
    Print.printLine("Drank Mega Potion! Nothing happened.");
}

Ugh. Copy-pasting this was a pain. Imagine if we had to change the message format, or if we had 10 items. Miserable. Absolutely miserable. Anyway, we can use it like this:

var inventory = [Potion, SuperPotion, MegaPotion];
inventory[0]();
inventory[1]();
inventory[2]();

With the output:

Drank Potion! Recovered 10 HP.
Drank Super Potion! Recovered 20 HP.
Drank Mega Potion! Recovered 45 HP.

A smarter way

These potions do almost exactly the same thing. Let's reflect that in our code. We express the idea of a healing item once and only once. In fact, there's no reason to restrict it to HP.

function HealingItem(name, stat, amount) {
  return function () {
    var oldStat = hero[stat];
    hero[stat] += amount;
    if (hero[stat] > hero[stat + "Max"]) hero[stat] = hero[stat + "Max"];
 
    var heal = hero[stat] - oldStat;
    if (heal > 0)
      Print.printLine("Drank " + name + "! Recovered " + heal + " " + stat.toUpperCase() + ".");
    else
      Print.printLine("Drank " + name + "! Nothing happened.");
  };
}
 
function Potion() {
  return HealingItem("Potion", "hp", 10);
}
 
function SuperPotion() {
  return HealingItem("Super Potion", "hp", 20);
}
 
function MegaPotion() {
  return HealingItem("Mega Potion", "hp", 50);
}

Here, the higher-order function is HealingItem(), because, as you can see, it returns a function as its result. Note that even after HealingItem() has returned, name, stat and amount are still "remembered". This funky property of returned functions is known as closure. We won't cover that here, but suffice to say, they're useful. Potion(), SuperPotion() and MegaPotion() just wrap conveniently around HealingItem() so we don't have to provide all the mundane parameters such as the name, statistic and amount each time. We call them wrappers: they make code easier to read.

We can use them as before:

var inventory = [Potion(), SuperPotion(), MegaPotion()];
inventory[0]();
inventory[1]();
inventory[2]();

Producing the same output:

Drank Potion! Recovered 10 HP.
Drank Super Potion! Recovered 20 HP.
Drank Mega Potion! Recovered 45 HP.

Note that Potion(), SuperPotion() and MegaPotion() now make items, so we need to put the parentheses there to "manufacture" the potions.

This demonstrates both the power of JavaScript's functions, but also the flexibility provided by its reflection: seeing and using program info when the program is running. Thanks to not restricting ourselves to HP, we can make ethers too:

function Ether() {
  return HealingItem("Ether", "mp", 10);
}
 
function SuperEther() {
  return HealingItem("Super Ether", "mp", 20);
}
 
function MegaEther() {
  return HealingItem("Mega Ether", "mp", 50);
}

In the chump's way, this would have doubled the size of the code.