Reputation: 15498
I am teaching a course that includes explaining functional JavaScript and I want to have a really good example of functional programming that is hopefully cleaner then non-functional. I want to convert the following switch statement to functional. I've made an example of that conversion myself, but hoping there is a simpler solution.
Here is the switch statement version:
let animalType = "Poodle";
switch (animalType) {
case "Poodle":
case "Beagle":
case "Bulldog":
console.log(animalType + " is a dog.");
break;
case "Bengal":
case "Siamese":
console.log(animalType + " is a cat.");
break;
default:
console.log(animalType + " is not a dog or cat.");
break;
}
And here is what I came up with as functional that I'm not that happy about
const result = getAnimalType("Poodle");
console.log("result:" + result)
function getAnimalType(animal) {
function isDog(animal) {
const dogs = ["Poodle", "Beagle", "Bulldog"];
return dogs.includes(animal)
}
function isCat(animal) {
const cats = ["Bengal", "Siamese"];
return cats.includes(animal)
}
return isDog(animal)
? animal + " is a dog."
: isCat(animal)
? animal + " is a cat."
: animal + " is not a dog or cat.";
}
Upvotes: 0
Views: 1228
Reputation: 18921
Working with maps would be my preferred option in this case. The question is how. I like curried functions so I'll use those:
const animalType =
(type => animal =>
type[animal]
? `${animal} is a ${type[animal]}`
: `${animal} is neither a dog nor a cat`)
({ Poodle: 'dog'
, Beagle: 'dog'
, Bulldog: 'dog'
, Bengal: 'cat'
, Siamese: 'cat'});
animalType('Poodle');
//> 'Poddle is a dog'
What's going on here?
We have a function that takes a map then returns a function that finally takes an animal and returns a message. The key is to realise that we also use an IIFE:
const animalType =
(type => animal => {/*…*/})({Poodle: 'dog'});
// ^^^^ ^^^^^^^^^^^^^^^
// | |
// +--------------------------+
The function animalType
is "hardcoded" to work with the type
we gave it. Now it just waits for the animal
parameter to come in.
However this seems like something we could abstract and reuse:
const lookup =
(pass, fail, map) => key =>
key in map
? pass(key, map[key])
: fail(key);
The lookup
function takes a success function pass
and a failure function fail
and an initial map
. Then it returns a function that takes a key
and applies pass
to key
and the corresponding value if the key exists in the map. Otherwise it applies fail
to the key.
With that we can build animalType
this way:
const animalType =
lookup( (key, val) => `${key} is a ${val}`
, (key) => `${key} is neither a dog nor a cat`
, { Poodle: 'dog'
, Beagle: 'dog'
, Bulldog: 'dog'
, Bengal: 'cat'
, Siamese: 'cat'});
animalType('Poodle');
//> 'Poddle is a dog'
Upvotes: 1
Reputation: 1375
I get something similar to this by compiling a TypeScript enum code example:
var AnimalType;
(function (AnimalType) {
AnimalType[AnimalType["Poodle"] = 0] = "Poodle";
AnimalType[AnimalType["Beagle"] = 1] = "Beagle";
AnimalType[AnimalType["Bulldog"] = 2] = "Bulldog";
AnimalType[AnimalType["Bengal"] = 3] = "Bengal";
AnimalType[AnimalType["Siamese"] = 4] = "Siamese";
})(AnimalType || (AnimalType = {}));
function whichKind(animalType) {
let kind;
switch (animalType) {
case AnimalType.Poodle:
kind = `${AnimalType[animalType]} is a dog`;
break;
case AnimalType.Beagle:
kind = `${AnimalType[animalType]} is a dog`;
break;
case AnimalType.Bulldog:
kind= `${AnimalType[animalType]} is a dog`;
break;
case AnimalType.Bengal:
kind = `${AnimalType[animalType]} is a cat`;
break;
case AnimalType.Siamese:
kind = `${AnimalType[animalType]} is a cat`;
break;
}
kind = typeof kind === 'undefined' ? `${animalType} is not a dog or cat.` : kind;
return kind;
}
console.log(whichKind(AnimalType.Poodle)); // Poodle is a dog
// console.log(whichKind('Other')); // Other is not a dog or cat.
Upvotes: 0
Reputation: 664936
There's nothing wrong with switch
from the perspective of functional programming. The problem rather is that it calls console.log
as a side effect, instead of returning a value. Easy to fix, though:
function getAnimalType(animalType) {
switch (animalType) {
case "Poodle":
case "Beagle":
case "Bulldog":
return animalType + " is a dog.";
case "Bengal":
case "Siamese":
return animalType + " is a cat.";
default:
return animalType + " is not a dog or cat.";
}
}
console.log(getAnimalType("Poodle"));
Next improvement might be avoiding some duplication:
function getAnimalType(animalType) {
switch (animalType) {
case "Poodle":
case "Beagle":
case "Bulldog":
return "a dog";
case "Bengal":
case "Siamese":
return "a cat";
default:
return "not a dog or cat";
}
}
function getStatement(animalType) {
return animalType + " is " + getAnimalType(animalType) + ".";
}
console.log(getStatement("Poodle"));
Upvotes: 1
Reputation: 22335
a variant, a bit like Barmar's, but which remains personal to me
const getAnimalType = (() =>
{
const
isX =
{ dog : 'is a dog'
, cat : 'is a cat'
, nDC : 'is neither a dog nor a cat'
}
, typeMap =
{ Poodle : 'dog'
, Beagle : 'dog'
, Bulldog : 'dog'
, Bengal : 'cat'
, Siamese : 'cat'
};
return (animal) => `${animal} ${isX[ typeMap[animal] ?? 'nDC']}`
})()
console.log(getAnimalType('Beagle'))
console.log(getAnimalType('Bengal'))
console.log(getAnimalType('schtroumpf'))
.as-console-wrapper {max-height: 100% !important;top: 0;}
.as-console-row::after {display: none !important;}
Upvotes: 1
Reputation: 815
You can create a really simple 3-line function for this
const dogs = ["Poodle", "Beagle", "Bulldog"];
const cats = ["Bengal", "Siamese"];
const getAnimalType = (animal) => {
if(dogs.includes(animal)) return `${animal} is a dog`
if(cats.includes(animal)) return `${animal} is a cat`
return `${animal} is not a dog or cat.`
}
const result = getAnimalType("Poodle");
console.log("result:" + result)
Upvotes: 1
Reputation: 781726
You can use an object to map animal types to functions.
function dog(animalType) {
return animalType + " is a dog.";
}
function cat(animalType) {
return animalType + " is a cat.";
}
function other(animalType) {
return animalType + " is not a dog or cat.";
}
const typeMap = {
Poodle: dog,
Beagle: dog,
Bulldog: dog,
Bengal: cat,
Siamese: cat
};
function getAnimalType(animalType) {
let typeFun = typeMap[animalType] || other;
return typeFun(animalType);
}
console.log(getAnimalType("Poodle"));
Upvotes: 3
Reputation: 370989
One option is an object indexed by dog or cat, whose values are arrays of animal types. This is easily extensible to additional animal types.
const animalNamesByType = {
dog: ["Poodle", "Beagle", "Bulldog"],
cat: ["Bengal", "Siamese"]
};
function getAnimalType(animal) {
const entry = Object.entries(animalNamesByType).find(
entry => entry[1].includes(animal)
);
return entry
? `${animal} is a ${entry[0]}`
: `${animal} is not in animalNamesByType`;
}
console.log(getAnimalType("Poodle"));
Upvotes: 1