Concept of First Class Citizens
What is a first class citizen of a programming language?
An entity is called a first class citizen if it satisfies these 3 conditions:
- We should be able to assign that entity to a variable
- We should be able to pass it to a function
- We should be able to return it from a function
Let us consider some common examples.
Numbers in a language are first class because they satisfy the above 3 conditions.
- We can assign numbers to variables.
let a = 10;
- We can pass a number to a function.
function foo(x) {
// do something with x
}
foo(10);
- We can return a number from a function.
function foo() {
return 10;
}
let x = foo();
In JavaScript, functions are also first class! This means:
- We can assign a function to a variable
- We can pass a function to a function
- We can return a function from a function
Beginners may find this a bit hard to understand. So let us take up some examples and understand this concept better.
Assigning a Function to a Variable
Consider the following:
var x = 5;
What does the above do?
- We create a value
5
in memory. - We create a variable
x
. - We make the variable
x
refer to (or point to) the value5
.
We can imagine that the variable x
is in one location and the value 5
is in another and x
is containing the address of the location 5
.
What happens when we assign a new value to x
?
var x = 5;
x = "Hello";
A new value Hello
is created in a different location and the same variable x
is now made to refer to this new value.
What happens to the value 5
? Generally speaking, values that are no longer referred to by any variable will be discarded. In other words, the memory occupied by such values will be reclaimed. (There are some optimizations that JavaScript engines implement, so strictly speaking, this may not be true for values like 5
, but you can ignore that for now.)
Let us consider one more example:
var x = 5;
x = x + 5;
A new value 10
is created in a new location and x
now refers to this location. Note that in JavaScript, numbers and strings are immutable, i.e. they can only be created, the values cannot be modified in-place once created.
Now consider this example:
var x = 5;
var y = x;
When we say var y = x
, we mean that y
must refer to the same value as what x
is referring to.
With this understanding, let us now look at functions in JavaScript.
What happens when we create a function in JavaScript?
Consider the following example:
function sum(x, y) {
return x + y;
}
3 things happen:
- An object containing the function definition is created in memory. Here, when I say object, think of it as something that occupies space in memory. We will call this "function object" in future.
- A variable by the name
sum
is created. - The variable is made to refer to the function object.
This is not very different from what happens when we say var x = 5
isn't it?
Note that sum
here is also a variable. This variable has no special significance.
In fact, it's perfectly valid to write this:
var sum = function sum(x, y) {
return x + y;
}
sum(2, 3);
The above statement has 2 references to the word sum
. One is a variable and another comes as part of the function. The second occurrence is called a function name.
Which sum
are we refering to when we call:
sum(2, 3)
Is it the variable sum
or the function name sum
?
To understand this, consider the following example:
var x = 5;
console.log(x);
We are using the variable name, x
to refer to the value 5
isn't it?
Similarly, it's the variable name sum
that we refer to when calling the function sum(2, 3)
, not the function name. This is clear from the following example:
var foo = function bar(x, y) {
return x + y;
}
> bar(2, 3)
Uncaught ReferenceError: bar is not defined
> foo(2, 3)
5
If that's the case, why have the function name at all? Can we omit it? Let's consider this example:
var sum = function(x, y) {
return x + y;
}
sum(2, 3)
It works! So we can define functions without a name and it works fine. Such functions (that don't have a name) are called anonymous functions.
Why would we ever need to name a function?
Naming a function is useful if we intend to refer to the function name inside the function (example, in recursive functions).
The name of the function becomes available in the function scope when the function is called.
var bar = function foo() {
console.log(foo);
}
> bar()
[Function: foo]
Just like how numbers, strings, etc are types, a function object is another type in JavaScript.
> var sum = function(x, y) { return x + y; }
undefined
> typeof(sum)
'function'
Note that when we say type(sum)
, we are asking what is the type of the value that the variable sum
refers to. The variable sum
has no special significance because it is referring to a function. We can use the same variable to refer to a value of a different type.
In the following example, we are making the sum variable refer to a number:
> sum = 10
10
> typeof(sum)
'number'
>
What happens if we now "call" sum as if it were a function?
> sum(2, 3)
Uncaught TypeError: sum is not a function
JavaScript is telling us that the variable sum
is not referring to an object of type function
; therefore it doesn't make sense to "call" it.
When a variable refers to an object of type function, then we can call it:
> var sum = function(x, y) { return x + y;}
undefined
> typeof(sum)
'function'
> sum(2, 3)
5
What happens if we assign add = sum
?
Remember that when we say variable2 = variable1
, we are saying that we want variable2
to refer to the same object as what variable1
is referring to.
So when we say add = sum
, we are making the variable add
refer to the same function object as what sum
is referring to.
> var add = sum;
undefined
What is the type of add
?
Since add
is referring to the same function object, its type is obviously function
. Therefore we must be able to call it.
Calling a function essentially means that we want to execute the function definition present in the function object.
> typeof(add)
'function'
> add(2, 3)
5
>
What happens if sum
now refers to something else?
> sum = 10
10
Can we still call add(x, y)
?
Since add
still refers to the function object, we must be able to call it:
> add(2, 3)
5
>
And there we have it! We have assigned a function (sum
) to a variable add
. This covers the first case of first class functions!
Also, to be clear, there is a difference between:
let add = sum;
v/s
let add = sum(2, 3);
add = sum
is a case of first-class functions. It means letadd
refer to the same object assum
.add = sum(2, 3)
is about assigning the return value ofsum
to the variableadd
. This is NOT a case of first-class functions.
Passing a Function to a Function
Study the following code carefully:
function fooCaller(foo) {
foo();
}
function bar() {
console.log('Bar');
}
fooCaller(bar);
Let us break this down:
function fooCaller(foo) {
foo();
}
What happens after execution of the above 3 lines?
- The function object
fooCaller
is created. - The variable
fooCaller
is created. fooCaller
refers to the function object.
Does foo
get created?
No! The arguments of a function, like foo
are created only when the function is called. It is important to note the difference between a function call and a function definition. We have only defined the function fooCaller
so far. We have not yet called it. So neither is foo
created nor does the body of the fooCaller
get executed.
Let us now consider the next 3 lines:
function bar() {
console.log('Bar');
}
What happens when the above 3 lines are executed?
- The function object
bar
is created. - The variable
bar
is created. - The variable
bar
refers to the function object.
bar
is not called yet (this may seem obvious but I have seen that beginners sometimes start forgetting basics of functions when learning first class functions 😃).
Now comes the interesting line:
fooCaller(bar);
What happens when this line is executed?
fooCaller
is called.bar
is passed tofooCaller
and it is received asfoo
. This means,foo
is created and is assignedbar
. This means,foo
now refers to whatbar
is referring to, i.e. the function `bar.foo
is called, which means we execute the body of the function object thatfoo
is referring to, which is nothing but thebar
function.- This prints
Bar
in the console.
What is the use of this? We could called bar
and that would print Bar
as well. Why pass it to fooCaller
and get fooCaller
to call it?
To appreciate this, let us consider some applications of first-class functions.
Applications of First Class Functions
Before we answer the question about why we need these concepts, let us consider some basics.
What does a computer do? A computer does: Input -> Process -> Output
What do we do in a programming language? We define how data is brought into RAM (Input), how we process it (Process) and what to do with the result (Output).
When data is in RAM, we give it a meaning - in the form of data types. First, we have primitive data types and operations on them. Then we have composite data types and operations on them.
Consider the following example:
Given a list of person objects, give me a list of their names.
> let people = [{name: 'John', email: '[email protected]'}, {name: 'George', email: '[email protected]'}]
undefined
> let peopleNames = []
undefined
> for(let i = 0; i < people.length; i++) {
... peopleNames.push(people[i].name);
... }
2
> peopleNames
[ 'John', 'George' ]
>
We do a lot of similar "data processing" operations. What if we had better, concise constructs to work with structures like these?
That's what we can do with application of some functional programming concepts.
There are several patterns of data transforms that have been identified. And there are some simple operations in functional languages to apply these data transforms. Let us look at a few of them.
Applications of Passing Functions to Functions
map
Function
A very common operation we perform when working with data is, "given a collection of n items, get me a corresponding collection of n other items", where the input and output items have some relationship.
For example, given a list of numbers, give me a list of the squares of the numbers.
How can we do this (without first-class functions)?
function getSquares(inputNums) {
let squaresList = [];
for(let num in inputNums) {
squaresList.push(inputNums[num] * inputNums[num]);
}
return squaresList;
}
let nums = [1, 2, 3];
let squares = getSquares(nums);
console.log(squares); // [1, 4, 9]
What if we want cubes?
I have seen learners saying, "Same thing, except do inputNums[num] * inputNums[num] * inputNums[num]
". How we wish, we could just tell a computer, "Same thing" and it could understand us!
But, in the approach we have used, we will have to write the whole code:
function getCubes(inputNums) {
let cubesList = [];
for(let num in inputNums) {
cubesList.push(inputNums[num] * inputNums[num] * inputNums[num]);
}
return cubesList;
}
let nums = [1, 2, 3];
let cubes = getCubes(nums);
console.log(cubes); // [1, 8, 27]
Wouldn't it be nice if we could abstract this out?
We do see a pattern in the getSquares
and getCubes
functions. Both are taking an input list and operating on each item of the input list and the result of the operation is being added to an output list, which is then returned.
What if we could abstract the common functionality into a function and somehow pass only the unique functionality of computing square
or cube
to this common function?
This is exactly what a map
does! The code may look like this:
function map(inputList, f) {
var outputList = [];
for(var index in inputList) {
outputList.push(f(inputList[index]));
}
return outputList;
}
var nums = [1, 2, 3];
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
console.log(map(nums, square)); // [1, 4, 9]
console.log(map(nums, cube)); // [1, 8, 27]
We have now written a generic function that can be used to convert a n-item input list to a n-item output list.
We can simplify this further by defining the function passed to map
on the fly during the invocation of map
:
function map(inputList, func) {
var outputList = [];
for(var index in inputList) {
outputList.push(func(inputList[index]));
}
return outputList;
}
var nums = [1, 2, 3];
console.log(map(nums, function square(x) {
return x * x;
}));
console.log(map(nums, function cube(x) {
return x * x * x;
}));
We can make functions anonymous:
function map(inputList, func) {
var outputList = [];
for(var index in inputList) {
outputList.push(func(inputList[index]));
}
return outputList;
}
var nums = [1, 2, 3];
console.log(map(nums, function(x) {
return x * x;
}));
console.log(map(nums, function(x) {
return x * x * x;
}));
We can even convert them to arrow functions:
function map(inputList, func) {
var outputList = [];
for(var index in inputList) {
outputList.push(func(inputList[index]));
}
return outputList;
}
var nums = [1, 2, 3];
console.log(map(nums, (x) => {
return x * x;
}));
console.log(map(nums, (x) => {
return x * x * x;
}));
If the function body of the arrow function is a single statement, we don't need curly braces or the return
keyword:
function map(inputList, func) {
var outputList = [];
for (var index in inputList) {
outputList.push(func(inputList[index]));
}
return outputList;
}
var nums = [1, 2, 3];
console.log(map(nums, x => x * x));
console.log(map(nums, x => x * x * x));
In fact, we don't even need to define our own map
function. There is already a built-in map
available to be applied on arrays:
var nums = [1, 2, 3];
console.log(nums.map(x => x * x));
console.log(nums.map(x => x * x * x));
Let us try this on a few more examples.
Given a list of strings (which are numeric) convert it to a list of numbers. Would this be a case of a map
?
- We are converting n-inputs to n-outputs.
- There is a relationship between one input item and its corresponding output item. Eg: The string
'199'
would become the number199
in the output.
How do we convert one input to one output? Given a string
, how can we convert it to a number
?
let inputString = '199'
let outputNumber = Number(s)
console.log(outputNumber); // 199
Once we know how to convert one input to one output, we can then pass the function to a map
to be applied on all inputs as follows:
var nums = ['199', '23', '34', '566'];
console.log(nums.map(s => Number(s)));
Another example:
Given a list of people, get me their names.
var people = [
{ name: 'John', email: '[email protected]', location: 'London' },
{ name: 'George', email: '[email protected]', location: 'Paris' },
{ name: 'David', email: '[email protected]', location: 'New York' }
];
function getNameFromPersonObject(person) {
return person.name;
}
console.log(people.map(getNameFromPersonObject));
Or even better with arrow functions:
console.log(people.map(person => person.name));
Given a list of people, generate an email addresses for the people based on their names. The email is their name in lower case + '@example.com'.
var people = [
{ name: 'John', location: 'London' },
{ name: 'George', location: 'Paris' },
{ name: 'David', location: 'New York' }
];
console.log(
people.map(
person => person.name.toLowerCase() + '@example.com'
)
);
Given a list of people with 2 fields (name and location), compute an email field using the name as before, and return a list of people with 3 fields (name, email and location).
var people = [
{ name: 'John', location: 'London' },
{ name: 'George', location: 'Paris' },
{ name: 'David', location: 'New York' }
];
console.log(
people.map(person => {
return {
name: person.name,
email: person.name.toLowerCase() + '@example.com',
location: person.location
};
})
);
If we don't want to use return
in our arrow function, we can use ()
to indicate that this is a single expression that needs to be returned.
var people = [
{ name: 'John', location: 'London' },
{ name: 'George', location: 'Paris' },
{ name: 'David', location: 'New York' }
];
console.log(
people.map(person => ({
name: person.name,
email: person.name.toLowerCase() + '@example.com',
location: person.location
})
)
);
We can use map to get a subset of the fields, to add new fields, to modify one or more fields and get a new list based on the input list. Let us explore some more examples:
Using object destructuring syntax to add fields:
var people = [
{ name: 'John', location: 'London' },
{ name: 'George', location: 'Paris' },
{ name: 'David', location: 'New York' }
];
console.log(
people.map(
({name, location}) => ({
name,
email: name.toLowerCase() + '@example.com',
location
})
)
);
If order is not important, we can even use the following syntax:
var people = [
{ name: 'John', location: 'London' },
{ name: 'George', location: 'Paris' },
{ name: 'David', location: 'New York' }
];
console.log(
people.map(
person => ({
...person,
email: person.name.toLowerCase() + '@example.com'
})
)
);
Using object destructuring to remove a field from a set of fields:
var people = [
{ name: 'John', age: 20, location: 'London' },
{ name: 'George', age: 30, location: 'Paris' },
{ name: 'David', age: 40, location: 'New York' }
];
function getPersonWithoutAge({age, ...rest}) {
return rest;
}
console.log(people.map(getPersonWithoutAge));
Or with arrow:
const people = [
{ name: 'John', age: 20, location: 'London' },
{ name: 'George', age: 30, location: 'Paris' },
{ name: 'David', age: 40, location: 'New York' }
];
const getPersonWithoutAge = ({ age, ...rest }) => rest;
console.log(people.map(getPersonWithoutAge));
A few more examples:
// Given a list of objects, get me a list of objects with one of the fields
console.log(people.map(({name}) => name));
// Given a list of objects, get me a list of objects with a subset of the fields
console.log(people.map(({name, age}) => ({name, age})));
// Given a list of objects, get me a list of objects with some fields removed
console.log(people.map(({age, ...rest}) => rest));
// Given a list of objects, get me a list of objects with some fields updated
console.log(people.map(({ age, ...rest }) => ({ age: age + 1, ...rest })));
// Another example
var contacts = ['Gautham,Bangalore', 'Jatin,Mumbai'];
var contact_objs = contacts.map(contact => {
var [name, place] = contact.split(',');
return {name, place};
});
console.log(contact_objs);
filter
Function
A filter
is used when we have n inputs and want a subset of n as output based on a condition.
Let us say we have a list of numbers and want a list of the even or odd numbers.
The function passed to filter works by taking one input item at a time and returning either true
if the condition is satisfied or false
if the condition is not satisifed.
var nums = [1, 2, 3, 4, 5];
function filter(inputList, func) {
var outputList = [];
for (var index in inputList) {
if (func(inputList[index])) {
outputList.push(inputList[index]);
}
}
return outputList;
}
function isOdd(num) {
return num % 2 == 1;
}
function isEven(num) {
return num % 2 == 0;
}
console.log(filter(nums, isOdd));
console.log(filter(nums, isEven));
Let us consider some practical applications of filter
.
Given a list of people, get me the list of people from London.
var people = [
{ name: 'John', age: 20, location: 'London' },
{ name: 'George', age: 30, location: 'Paris' },
{ name: 'David', age: 40, location: 'London' }
];
We start by defining a function that works on one object, that returns true/false based on the condition (is the person from "London"):
function isPersonFromLondon(person) {
return person.location === 'London';
}
Now pass this function to the filter
:
console.log(people.filter(isPersonFromLondon));
Or with arrow function:
console.log(people.filter(person => person.location === 'London'));
A few more examples:
// People with age >= 30
console.log(people.filter(person => person.age >= 30));
// People from London who are aged >= 30
console.log(people.filter(person => person.location === 'London' && person.age >= 30));
// Given a list of people, give me a new list without the person John
console.log(people.filter(person => person.name !== 'John'));
Applying filter
s and map
s in a pipeline
We can combine filter
and map
in a pipeline.
Given a list of people, get me a list of the names of the people from London.
var people = [
{ name: 'John', age: 20, location: 'London' },
{ name: 'George', age: 30, location: 'Paris' },
{ name: 'David', age: 40, location: 'London' }
];
console.log(people
.filter(({location}) => location === 'London')
.map(({name}) => name));
👉 A keen observer will perhaps realize that the effect of applying a filter
and a map
to an input list is akin to applying a WHERE
and SELECT
clause respectively to a table in a relational database. It is also similar to applying a grep
followed by a cut
or an awk '{print ...}
on an input in the Linux command line.
Returning a Function from a Function
Study the following code carefully:
function foo() {
function bar() {
console.log('Bar');
}
return bar;
}
var x = foo();
x() // Prints 'Bar'
Let us break this down. What happens after the following is executed:
function foo() {
function bar() {
console.log('Bar');
}
return bar;
}
- The function object
foo
is created. - The variable
foo
is created. - The variable
foo
refers to the function object.
Is bar
created?
No!
Is bar
called?
No!
Note that bar
is defined in the body of foo
. Anything that is defined in the body of a function is executed only if the function is called. We haven't called foo
yet. So neither is bar
created, nor is it called.
What happens when we execute this:
var x = foo;
x();
That was a trick question. Note that when we say x = foo
, it means let x
refer to the same object as what foo
is referring to, which is the first case of first-class functions. So when you call x()
, it means, execute the body of foo
.
However, the meaning completely changes when we do this:
var x = foo();
x()
This invokes foo
and the return value of foo
is assigned to x
. What does foo
return?
Let us analyse the function foo
:
function foo() {
function bar() {
console.log('Bar');
}
return bar;
}
When foo
is invoked, the bar
variable is created and it refers to the bar
function object. This is what is then returned and we assign the return value to x
. Thus x
is a reference to the return value of foo
, which is nothing but the function object bar
.
Since bar
is defined in the function foo
, we say that bar
is an inner function of foo
.
What is the typeof(x)
?
typeof(x) // 'function'
Since x
is referring to a function, we can call it. When we call x()
, the body of the function that x
refers to (which is nothing but bar
) is executed. This is what ends up printing "Bar".
We can simplify the inner function by defining and returning the function in one statement:
function foo() {
return function bar() {
console.log('Bar');
}
}
We can make it anonymous as well:
function foo() {
return function() {
console.log('Bar');
}
}
var x = foo();
x();
Finally, we can even make it an arrow function:
function foo() {
return () => {
console.log('Bar');
}
}
If a function call returns a function, then we can call the outer function followed by the inner function in one single statement:
foo()()
Closures
function foo(x) {
var tmp = 20;
function bar(y) {
console.log(x + y + tmp);
}
return bar;
}
var p = foo(10);
p(30);
# Set a breakpoint on Line 4
# Run the program in Debug mode
# In Visual Studio Code, it's under Run -> Start Debugging
# The variable y is in the Local scope of bar
# The variables x and tmp are in the Closure scope of foo