Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Salvatore D'Agostino
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Salvatore D'Agostino ( @gimli )

The Best Way To Compute The Average Age Of Cat Owners In Functional Programming

By on

In JavaScript, there is an ever-increasing adoption of functional style programming in our modern web applications. And, even in code that isn't purely functional, developers are often using functional aspects to map, reduce, filter, and sort values. It's really cool (and really fun) stuff; and with excellent support for ES5 in modern browsers, functions like .map(), .filter(), .reduce(), and Object.keys() can even be used without the help of libraries like Lodash.

With so much functionality at our fingertips, it can be hard to know the best way to do something. Take, for example, trying to find the average age of cat owners in a given collection. With functional programming, we have a lot of ways to get to the same answer. Here are just a few that I could come up with:

var _ = require( "lodash" );

// Assume a collection that has:
// - age: Number
// - hasCat: Boolean
var people = require( "./people.json" );

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //


var reduction = _.reduce(
	people,
	function operator( accumulator, person ) {

		if ( person.hasCat ) {

			accumulator.sum += person.age;
			accumulator.count++;
			accumulator.average = ( accumulator.sum / accumulator.count );
		}

		return( accumulator );

	},
	{
		sum: 0,
		count: 0,
		average: 0
	}
);

console.log( "Average age of cat owner: %s", reduction.average );


// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //


var averageAge = _.reduce(
	_.pluck(
		_.where(
			people,
			{
				hasCat: true
			}
		),
		"age"
	),
	function operator( average, age, index, collection ) {

		return( average + ( age / collection.length ) );

	},
	0
);

console.log( "Average age of cat owner: %s", averageAge );


// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //


var catOwners = _.where( people, { hasCat: true } );
var averageAge = ( _.sum( _.pluck( catOwners, "age" ) ) / catOwners.length );

console.log( "Average age of cat owner: %s", averageAge );


// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

var averageAge = _.chain( people )
	.where({ hasCat: true })
	.pluck( "age" )
	.reduce(
		function operator( sum, age, index, collection ) {

			return ( index === ( collection.length - 1 ) )
				? ( ( sum + age ) / collection.length )
				: ( sum + age )
			;

		},
		0
	)
	.value()
;

console.log( "Average age of cat owner: %s", averageAge );

These are all somewhat similar and somewhat different. And, they all do the same thing - compute the average age of cat owners. But, which way is the best?

None of them. They're all bad.

The best way to compute the average age of cat owners would be to write:

console.log( "Average age of cat owner: %s", getAverageAgeOfCatOwners( people ) );

So, clearly this is a little tongue-in-cheek. But, I just wanted to throw this out there as a gentle reminder that the readability of code is super important. Just because something can be computed with a complex combination and application of functions, it doesn't mean that it's easy to read or to understand. In functional programming, you can still hide implementation details. You can still create code that is obivous in its intent. And, more importantly, obvious to the next developer.

Want to use code from this post? Check out the license.

Reader Comments

2 Comments

Do you also advocate the usage of parseInt() for converting a string to an integer, rather than the unary + operator, or multiplying by 1, since parseInt() more clearly expresses intent? I myself do.

15,674 Comments

@George,

Honestly, I go back and forth on what I feel comfortable with. I use all of them a bit, in different situations. The problem with parseInt() is that most people forget to pass-in the base-10 radix as the second, optional parameter. The browsers may default to base-10 now-a-days; but, I'm pretty sure this used to cause some weird "octal" edge-case bugs.

If I can encapsulate the access to the value, such as something like:

function getUserID() {
. . . return( + $routeParams.userID || 0 );
}

... then I'll usually use the "+" operator (or at least what I've been doing lately). But, I am fine with that because the parent function is what has to be most understandable. Then, the parsing just becomes an implementation detail that can be swapped out.

So, long story short, I have used all three approaches. And in each situations, I probably [hopefully] try to choose the one that seems like it has the least amount of "magic".

2 Comments

Yeah the optional second argument is something to consider with parseInt() and I indeed ran into one of those octal issues back in 2001 IIRC. Speaking of magic though, I rely on some of parseInt's magic, such as ignoring any trailing non-digit input -- it's really great for instance when your input is something such as '20px' but you just want 20 as an integer from it.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel