1

I am working on my understanding of functional programming and have a question.

In this example* an anonymous function is assigned to the variable isDog, then isDog is passed to filter. Then we can filter all the dogs in the animals array into a new array. (I know I can shorten this code, but that is not the point of the post :) )

var animals = [{name: "Spot", species: "dog"}, 
{name: "Rex", species: "dog"}, 
{name: "Whiskers", species: "cat"},
{name: "Floppy", species: "Rabbit"}]

var isDog = function(animal) {return animal.species === 'dog' } 

var dogs = animals.filter(isDog)

I understand that functions can be passed as arguments and maybe that is made explicit by assigning functions to variables.

But now that the anonymous function is a variable and we don't write it with brackets i.e isDog(), intuitively it seems like this has made the code less readable. At first glance I would assume isDog is just a variable, and not a function.

Even if we can infer it is a function because it is attached to filter, it is still confusing and I assume there are other cases where it is not obvious. So now we have to look up what isDog is/does.

So what I am asking is why do this? It does look more elegant to have isDog without brackets, but is there any technical reason to use it this way?

Note, I understand the power of functions as arguments, my question is more about why assign them to a variable if it will make ambiguous code.

Thanks.

*Adapted from this helpful video. around 8min mark, https://www.youtube.com/watch?v=BMUiFMZr7vk

damtypo
  • 150
  • 1
  • 18
  • 1
    It can be useful when you have to use that same function again for another situation. – trincot Aug 19 '16 at 23:40
  • 3
    How does it make the code ambiguous? Giving a function a descriptive name makes it *less* ambiguous. You already know it's a function because `.filter()` has no use for anything else in that argument position. –  Aug 19 '16 at 23:40
  • Possible duplicate of [JavaScript function declaration syntax: var fn = function() {} vs function fn() {}](http://stackoverflow.com/questions/336859/javascript-function-declaration-syntax-var-fn-function-vs-function-fn) – Alexander Aug 19 '16 at 23:44
  • 1
    @damtypo are you seriously suggesting that `animals.filter(isDog)` is so ambiguous that to understand that line you need to look up *how* `isDog` works? What about `animals.sort(byBreedName)`? – zzzzBov Aug 20 '16 at 00:23
  • @zzzzBov Um no, you must not have read where I acknowledge the obviousness of the example in my question. – damtypo Aug 20 '16 at 01:01
  • Umm.. `isDog` *is* "just" a variable, and the value of the variable is a function. It's no different from passing the name of any function as an argument. – molbdnilo Aug 20 '16 at 08:15
  • I'll just point out you're in the same boat for *all* variables. No variables have marks that indicate their types. There's no reason variables holding references to functions should be any different. It just feels different because it's new to you. – Karl Bielefeldt Aug 21 '16 at 03:40

4 Answers4

2

The brackets are only there when you're calling the function. If you pass the call with the brackets into the .filter() method, you'll actually be passing the result of the call, rather than a pointer to the call itself. The alternative is to pass in the entire function into the filter function, but readability tends to suffer. It also limits your ability to modify what's going into the function you're putting it in.

Imagine a case where you might want to do a sort instead of a filter, and you wanted to change the sort based on a boolean passed in.

Take this basic sort for example:

var numbers = [4, 2, 5, 1, 3];

numbers.sort(function(a, b) {
  return a - b;
});

What if you wanted to be able to choose which direction it was sorted based on another variable. You can define both sort functions, and then pass the correct one for the sort you want.

var numbers = [4, 2, 5, 1, 3];
var sortAscending = function(a, b) {
  return a - b;
}

var sortDescending = function(a, b) {
  return b - a;
}

function doSort(myArray, dir) {
    var sortMethod = (dir == "asc") ? sortAscending : sortDescending;
    myArray.sort(sortMethod );
}

doSort(numbers, "asc");

The above illustrates how passing the method call this way allows for more flexibility when you need it, and ensures the call is made only when it's executed inside sort.

nixkuroi
  • 2,259
  • 1
  • 19
  • 25
2

We assign functions to variables for the same reason we assign values to variables. The variable is a labelled descriptor of intent.

If you read animals.filter(isDog) it should be blatantly obvious what to expect from the function.

That is to say, the expectation in the code is that you are taking the collection of animals and filtering it to only include the animals that are dogs.

This is no different than variable usage with any other value.

Take the following line for example:

var fiveMinutesAsMilliseconds = 5 * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;

You should be able to read that line of code and understand what sort of value fiveMinutesAsMilliseconds will contain without looking up either SECONDS_PER_MINUTE or MILLISECONDS_PER_SECOND.

If your functional code is ambiguous, then it is likely because you have named your variables poorly. Naming things is hard, so if variables are named badly, treat it as a bug and fix the names such that the code is understandable.

zzzzBov
  • 174,988
  • 54
  • 320
  • 367
1

Assigning an anonymous function to a variable only makes sense when you intend to use that function multiple times. Otherwise you can just pass it as a parameter directly to the filter() call. Also, when using methods like filter() it's better to use arrow functions. And as a side note, if you don't intend to reassign a variable later, it's better to use const instead of var – this will prevent you from reassigning that variable by mistake. See How much should I be using 'let' vs 'const' in ES6?.

To address your confusion on why isDog doesn't have brackets: brackets mean that your calling the function, whereas here you're simply passing this function as a parameter. filter(isDog()) would mean that you're calling the isDog function and then passing the returned value as a parameter to the filter() method. This would make sense only if isDog returned another function.

Your code could look like that:

const dogs = animals.filter(animal => animal.species === 'dog')
Community
  • 1
  • 1
Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
  • Arrow functions are only useful if you're in an environment where backward compatibility is not a factor, and you don't care about IE at all (as of 8/2016 anyway). Check out this link (compat stuff is at the bottom) to see where it's usable. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions If you're not running an ECMA 6 compatible browser, or using a transpiler, you're going to see a lot of breakage. In my job, I use them all the time because it's a closed R&D environment and we only use Chrome. I'd never use them in a public facing site though. – nixkuroi Aug 20 '16 at 00:55
  • Yeah, that's a transpiler :) Sometimes they're great, but it's easy to get annoyed if they don't produce the code you think they're going to, and those always seem to be edge cases that produce unpredictable results. – nixkuroi Aug 20 '16 at 01:04
0

But now that the anonymous function is a variable and we don't write it with brackets i.e isDog(), intuitively it seems like this has made the code less readable.

Well that depends on the function actually. What you need to be concerned about is what filter is asking for. filter expects a function, and if you apply isDog() (apart from the error that will be thrown), a function will not be returned.

Sometimes you will see brackets () in applications of filter

let greaterThan = x => y => y > x

[1,2,3,4,5,6].filter(greaterThan(3))
// => [ 4, 5, 6 ]

… because not only can functions be assigned to variables, they can also be return values from a function application.


So what I am asking is why do this? It does look more elegant to have isDog without brackets, but is there any technical reason to use it this way?

Well making isDog a separate function doesn't have a primary goal of making the code "look nice" – some might even argue it's more verbose and they might not like it.

The primary reason to make isDog its own function is because it's useful on its own. Maybe you have an x and you want to know if it's a dog?

isDog(x); // => true
isDog(y); // => false

If isDog wasn't its own function, your program would be littered with duplicate bits of is-dog checking code

// ex1
animals.filter(animal => animal.species === 'dog')

// ex2
if (x.species === 'dog') {
  throw Error('Dogs are not allowed !')
}

// ex3
function canCooperate (a,b) {
  switch (true) {
    case a.species === 'cat' && b.species === 'dog':
    case a.species === 'dog' && b.species === 'cat':
      return false
    default:
      return true
  }
}

It's much nicer to wrap the complexity of what defines an animal's kind in its own reusable function.

const isDog = ({species}) => species === 'dog'
const isCat = ({species}) => species === 'cat'

// ex1
let allDogs = animals.filter(isDog)

// ex2
if (isDog(x)) {
  throw Error('Dogs are not allowed !')
}

// ex3
function canCooperate (a,b) {
  switch (true) {
    case isCat(a) && isDog(b):
    case isDog(a) && isCat(b):
      return false
    default:
      return true
  }
}

isDog is more descriptive than x.species === 'dog' and more clearly communicates the purpose of the code. In fact, it's better to use isDog everywhere because other criteria might exist that changes what it means to be a dog.

const isDog = x => x.species === 'dog' || x.species === 'wolf'

Maybe not the best example, but not totally farfetched to think that some corner case might exist where you want to treat wolves the same. If you didn't have this written in its own function, isDog, you'd have to update every piece of code in your app to include the extra ... || x.species === 'wolf' check


At first glance I would assume isDog is just a variable, and not a function.

Even if we can infer it is a function because it is attached to filter, it is still confusing and I assume there are other cases where it is not obvious.

Well usually functions have a verb name, but that's not always the case. filter is both a noun and a verb making this argument very hard to make, but it's mostly an exception to the rule. I know you somewhat acknowledges this, but the short answer is: you don't know. Someone could be a true arsehole and name a function animals and it would still be valid JavaScript.


So now we have to look up what isDog is/does.

Meh, not necessarily. Convention can go a long way.

  • use verbs to name your functions: encode, concat(enate), write, reduce, parse, etc

  • function that return a boolean are commonly prefixed with isArray.isArray, Number.isNaN, Number.isFinite etc

  • name event handlers (functions) with on or handle prefix – onClick, handleClick, etc

  • class names should be a singular noun

  • the list goes on ...

There's tons of little things like this. And you don't have to follow everyone else's conventions; tho it would likely help you in the long run. Most importantly, adopt some sort of convention and stick with it.

Community
  • 1
  • 1
Mulan
  • 129,518
  • 31
  • 228
  • 259