New Refactoring: Replace Conditional With Lambda Map

There are tried-and-tested routes to replacing conditional statements that effectively map identities (e.g., if(x == “UK shipping”) ) to polymorphic implementations of what to do when an identity is determined (e.g. create a Shipping interface and then have a UKShipping class that knows what to do in that situation and pass that in to the method).

But sometimes I find, when the literal that represents the identity has to be preserved (for example, if it’s part of an API) that mapping identities to actions works better.

In these instances, I have found myself converting my conditionals to a map or dictionary instead. Each identity is mapped to a lambda expression that can be looked up and then executed.

Take this example of a function that scores games of Rock, Paper, Scissors in JavaScript:

const rock = "rock";
const scissors = "scissors";
const paper = "paper";
function play(game, player1, player2) {
let score = {game.score};
let winner = undefined;
if (player1 === rock) {
if (player2 === scissors) {
score.player1++;
} else {
if (player2 === paper) {
score.player2++;
}
}
}
if (player1 === paper) {
if (player2 === rock) {
score.player1++;
} else {
if (player2 === scissors) {
score.player2++;
}
}
}
if (player1 === scissors) {
if (player2 === paper) {
score.player1++;
} else {
if (player2 === rock) {
score.player2++;
}
}
}
if (score.player1 === 2) {
winner = 1;
}
if (score.player2 === 2) {
winner = 2;
}
return {winner: winner, score: score};
}
module.exports = {play, rock, paper, scissors};

view raw
game.js
hosted with ❤ by GitHub

The literals “rock”, “paper” and “scissors” have to be preserved because we have a web service that accepts those parameter values from remote players. (This is very similar to the Mars Rover kata in that respect, where R, L, F and B are inputs.)

First, I’d remove some obvious inner duplication in each outer IF statement.

const rock = "rock";
const scissors = "scissors";
const paper = "paper";
function play(game, player1, player2) {
let score = {game.score};
let winner = undefined;
if (player1 === rock) {
playHand(player2, score, scissors, paper);
}
if (player1 === paper) {
playHand(player2, score, rock, scissors);
}
if (player1 === scissors) {
playHand(player2, score, paper, rock);
}
if (score.player1 === 2) {
winner = 1;
}
if (score.player2 === 2) {
winner = 2;
}
return {winner: winner, score: score};
function playHand(opponent, score, beats, losesTo) {
if (opponent === beats) {
score.player1++;
} else {
if (opponent === losesTo) {
score.player2++;
}
}
}
}
module.exports = {play, rock, paper, scissors};

view raw
game.js
hosted with ❤ by GitHub

Now let’s replace those 3 IF statements that map actions to “rock”, “paper” and “scissors” into an actual map.

function play(game, player1, player2) {
let score = {game.score};
let winner = undefined;
const hands = {
rock: () => playHand(player2, score, scissors, paper),
paper: () => playHand(player2, score, rock, scissors),
scissors: () => playHand(player2, score, paper, rock)
};
hands[player1].call();

view raw
game.js
hosted with ❤ by GitHub

If we take a look inside playHand(), we have an inner conditional.

function playHand(opponent, score, beats, losesTo) {
if (opponent === beats) {
score.player1++;
} else {
if (opponent === losesTo) {
score.player2++;
}
}
}

view raw
game.js
hosted with ❤ by GitHub

This could also be replaced with a lambda map.

function playHand(opponent, score, beats, losesTo, draws) {
const opponents = {
[beats]: () => score.player1++,
[losesTo]: () => score.player2++,
[draws]: () => {}
};
opponents[opponent].call();
}

view raw
game.js
hosted with ❤ by GitHub

Note that I had to add a draws identity so that the mapping is complete. I’d also have to do this for any default case in a conditional (I suppose draws is the default case here, as nothing happens when there’s a draw – an empty lambda).

Author: codemanship

Founder of Codemanship Ltd and code craft coach and trainer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s