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 is
– Array.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.