I'm experimenting with functional programming using an array of objects.
I want to understand if there is a better or cleaner way to execute a series of functions that takes values and updates variables based on conditions.
Like the use of the globally scoped variables, is this frowned upon? Is there anyway I could pass lets say the count of the number of dogs to the "updateDogsAmt" function? And so on...
Is there a better way I could have done this?
I have divided out these functions between what I think is updating the DOM and logic.
Demo: JSFIDDLE
const animals = [
{name: 'Zack', type: 'dog'},
{name: 'Mike', type: 'fish'},
{name: 'Amy', type: 'cow'},
{name: 'Chris', type: 'cat'},
{name: 'Zoe', type: 'dog'},
{name: 'Nicky', type: 'cat'},
{name: 'Cherry', type: 'dog'}
]
let dogs = [];
function getDogs() {
//return only dogs
animals.map((animal) => {
if(animal.type === "dog") {
dogs.push(animal);
}
});
}
getDogs();
let dogsAmt = 0;
function getDogsAmt() {
//get dogs amount
dogsAmt = dogs.length;
}
getDogsAmt();
function updateDogsAmt() {
//update dom with dogs count
let dogsHTML = document.getElementById('dogs-amt');
dogsHTML.innerHTML = dogsAmt;
}
updateDogsAmt();
let otherAnimals = [];
function getOtherAnimals() {
//return other animals count
animals.map((animal) => {
if(animal.type != "dog") {
otherAnimals.push(animal);
}
});
}
getOtherAnimals();
let otherAnimalsAmt = 0;
function getOtherAnimalsAmt() {
otherAnimalsAmt = otherAnimals.length;
}
getOtherAnimalsAmt();
function updateOtherAnimalsAmt() {
//udate dom with other animals
let otherAmt = document.getElementById('other-amt');
otherAmt.innerHTML = otherAnimalsAmt;
}
updateOtherAnimalsAmt();
In functional programming, functions are pure, which means:
The functions you define are not pure, because they
So this function is impure:
let dogsAmt = 0;
function getDogsAmt() {
// do something with dogs and modify dogsAmt
}
while this one is pure:
function getDogsAmt(dogs) {
// do something with dogs and return dogsAmt
}
let dogsAmt = getDogsAmt(dogs);
Writing in a functional style makes it easy to reuse code. For example, in your example you only need one function for counting animals and updating the DOM, respectively:
const animals = [
{name: 'Zack',type: 'dog'},
{name: 'Mike',type: 'fish'},
{name: 'Amy', type: 'cow'},
{name: 'Chris', type: 'cat'}
];
function getDogs(animals) {
//return only dogs
return animals.filter(animal => animal.type === "dog");
}
function getOtherAnimals(animals) {
//return other animals count
return animals.filter(animal => animal.type !== "dog");
}
function getAmt(animals) {
//get number of animals in the array
return animals.length;
}
function updateHTML(id, amount) {
//update dom
document.getElementById(id).innerHTML = amount;
}
updateHTML('dogs-amt', getAmt(getDogs(animals)));
updateHTML('other-amt', getAmt(getOtherAnimals(animals)));
<p>There are <span id="dogs-amt">0</span> dogs</p>
<p>There are <span id="other-amt">0</span> other animals</p>
One function in this code is still not pure! updateHTML
takes two arguments, and always returns undefined
. But along the way, it is causing a side effect: it updates the DOM!
If you want to get around this issue of updating the DOM manually based on impure functions, i would recommend looking at libraries/frameworks like React, Elm, Cycle.js or Vue.js - they all make use of a concept called virtual DOM, which allows representing the entire DOM as a JS data structure and take care of synchronizing the virtual DOM with the real one for you.
Functional languages (e.g. Haskell, Lisp, Clojure, Elm) force you to write pure functions and introduce many mind-bending concepts when your background lies more in procedural or object-oriented programming. In my opinion, JavaScript is an excellent language to "stumble into" functional programming though. While it looks like Java at first glance, JS has much more in common with Lisp when you take a closer look. Trying to understand things like closures and prototypal inheritance instead of trying to write JS as if it was Java helped me a great deal. (A great read in this context: The Two Pillars of JavaScript)
For your next steps in functional JavaScript, i would recommend to
let
)After you got more comfortable with that, you can start tackling more advanced functional concepts (Higher-order functions, partial application, currying, immutability, monads, observables/functional reactive programming) bit by bit. Some interesting libraries in JavaScriptLand are
For me, the lightbulb of functional programming didn't go on by reading all about non-mutable state, first class, higher order, declarative syntax, and all that. OOP guys generally said, "but you can do all that in OOP".
So I got the academia, but I believed there had to be something more fundamental to it. I struggled on, and one day read the line (I forget where) "instead of fine grained, think course grained."
(Just an aside, while a lot of functional programming looks cryptic and uses all kinds of scary things like =>, _, and so on, it doesn't have to...it's more about how you approach and think).
I've used this successfully as an example before: a class with a whole bunch of iterations in it, you see them all the time in OOP (this is a naive example but you get the idea):
var someArr = [ 1, 2, 3 ];
var getSum = function ( arr ) { // iterate, get sum, return }
var getProd = function ( arr ) { // iterate, get prod, return };
So now I can do something like this:
var getProductTimesLength = getSum ( someArr ) * getProd ( someArr );
Functional right? Sort of like your posted code. Hmmm...not really. It's kind of declarative, but no higher order or first class functional use. How can we take a step in that direction?
Maybe create a single "higher order" function that takes instructions (a function) as an arg (a "first class" function is basically a func as an arg) and encapsulates iteration for...well, anything. In other words, take iteration out of fine-grained use and make it a course grained util. (Sure you could add to the array prototype, that's another discussion).
So now I might do this (again, totally naive, you'd want to make sure you're getting an array of numbers, check zero when needed, all that):
// This would probably be in a util module or some such.
var reduceArray = function ( arr, func ) {
var redux = 0;
for ( var each in arr ) { redux = func ( redux, arr [ each ] ); }
return redux;
}
// This is what would appear in your dev module.
var prod = function ( a, b ) { return a * b };
var sum = function ( a, b ) { return a + b; }
var sumProd = reduceArray ( arr, sum ) * reduceArray ( arr, prod );
Not the nirvana of functional, but a step in the right direction. Also, consider how it changes what you'd test. And, I could just pass the prod and sum functions as anon functions right in the reduceArray args themselves if I wanted, so I wouldn't even have to declare them:
// This is starting to look more like usual "functional" examples
var sum = reduceArray ( arr, function ( a, b ) { return a + b } );
var prod = reduceArray ( arr, function ( a, b ) { return a * b } );
var sumProd = sum * prod;
Or even...
var sumProd = reduceArray ( arr, function ( a, b ) { return a + b; } )
* reduceArray ( arr, function ( a, b ) { return a * b; } );
And of course functional guys seem to like it all in one statement:
var sumProd = reduceArray ( arr, function ( a, b ) {
return ( reduceArray ( arr, function ( a, b ) { return a + b } )
* reduceArray ( arr, function ( a, b ) { return a * b } ) );
}
A lot of people will say this is way harder to read. But it really isn't. You just have to look at it differently, and when you get it, this is actually simpler (though some devs go way too far, some of the Scala I've seen looks like three lines of random characters thrown at the screen).
With this is mind, take a look at your code again. I tried to make the example relevant to your code and I'm sure plenty of people will offer you specific rewrites. But until you get how thinking functional is actually different than thinking OOP, to me anyway, all the examples in the world may not help.
It changes the way you think about almost everything: for instance, in an iteration, you might hear an OOP guy insist that the incremental var should have a "meaningful" name. Something like "for ( name in ArrayNames ) { ... } );
But if you "course grain" iteration, there is no meaningful name other than maybe "item". So you may as well just use "val", or even just "d" or "v" (which you see all the time in functional examples). The specific type that is being iterated isn't critical as long as it can be iterated and as long as you tested your instruction function.
(OMG what did I say...the specific type doesn't really matter??? HE'S A WITCH BURN HIM!!!).
Again, a totally naive example but one I've used with success. Once people grok it, they say things like, "oh...but you could actually take those instructional funcs you defined, and add them to the util module, such that you could create reduceArray.prod ( myArray ), making the higher order util more robust...". Suddenly it's not all about classes and interfaces.
Yep.
Edit: Here's a quick stab at your original code to make it quasi-functional:
// Course-grain the dom manipulation, allows you to use it declaratively.
// See that now, you could check if selector returns an elem,
// if not, try it as a css style/class selector, and so on.
var updateDisplayElement = function ( selector, data ) {
var elem = document.getElementById(selector);
elem.innerHTML = data + ''; // make sure it's a string, whatever.
};
// Now this, much more terse etc.
var dogs = animals.filter ( function ( d ) { return animal.type === 'dog' } );
updateDisplayElement ( 'dogs-amt', dogs.length );
// Note that 'other' is just 'all - dogs'
updateDisplayElement ( 'other-amt', animals.length - dogs.length );
Again not functional nirvana, but a leap in the right direction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With