- COMP.CS.200
- 7. JavaScript: FP & async (assignment)
- 7.1 Functional JavaScript
Functional JavaScript¶
What is functional programming?¶
Programming paradigms can be divided into imperative and declarative. Imperative paradigm can be illustrated as a machine that solves the problem step-by-step. Imperative programming languages include C, Forthran, Pascal, and Ada. Object-oriented paradigm may be counted as one flavor of imperative paradigms. Declarative programming instructs the interpreter with overall description of how to transform the data – without going into details. Declarative languages are, e.g., SQL, XML and HTML.
This comparison is not to prove other paradigm’s superiority over the other: the paradigms are used for different purposes, where they both excel when appropriately used.
Example of imperative programming¶
In embedded development, imperative programming is a better-fit than declarative. If one was to program a washing machine, a program would consist of different steps that are repeated in a certain order.
While water level is under 23cm
Take water in
While temperature is under 40 degrees
Heat water
For 20 minutes sling
Remove water
Repeat steps twice
Open lock
A system controlling the washing machine is constantly aware of the state of the washing machine, such as water level, water temperature, executed steps, etc.
Example of declarative programming¶
The example of programming a washing machine is down-to-earth and easy to understand. However, similar examples about declarative programming is not that trivial to find. The two paradigms aim at solving completely different problems.
Yet we could take a look at Facebooks spam protection. Preferably, the detection of spam, phishing, malware and other types of abuse should be real-time. For this kind of problems, imperative programming suits bad: it would require a huge amount of if-else conditions, which, consequently, makes code slow to execute and difficult to maintain.
Instead, Facebook chose to exploit a functional approach. They wrote efficient functions that can be combined. The combinations may consist of hundreds of functions and they are referred to as higher-order functions. Every action in Facebook (likes, shares, updates, etc.) is checked against these rules. If a malicious action is detected, it will instantly be removed from other people’s news feeds.
It is typical for functional programming to be used to analyze and transform large amounts of data in a real-time. Previously, the amount of data was not that overwhelming. Big data was mentioned only in conjunction with heavy calculations concerning weather forecasts, cryptography, or financial matters, such as tax returns. The invent of Internet has revolutionized the field of big data. The amount of data has exploded along with the Internet expansion, information comes from variety of sources, e.g., due to the ubiquity of social sharing: today, every click you make in the internet gets transformed and analyzed in one way or another.
Functional programming¶
Functional programming (FP) belongs to the declarative programming paradigm. As you may have guessed, functions are in the core of FP. In FP, you create and combine functions to describe data transformations as function compositions.
There exist some “pure” FP languages like Haskell and Elm. JavaScript is not pure FP language but it can be used like one. By now, you have learned a little about JavaScript functions. Next, we will go through JavaScript code samples that concentrate on these functional features in particular.
Key characteristics of functional programming¶
Immutable data: You do not ever change a value of a variable. Let’s say that you have a chair object. This chair defines a property of its current location. If you want to move the chair object, you create a copy of the old chair and replace the old location with a new one. Sounds like a lot of work, but it makes tracking bugs way simpler, therefore the total amount of work may decrease.
Tips, use:
always
const
instead oflet
a library like Immutable for larger projects
Pure functions:
cause no side-effects such as
console.log()
, API calls, or global data modifications,return always the same output for the same input
In which situations can a function return a different value for the same parameters?
For example, if some conditions depend on global variables that can be muted in an outer scope.
If you are using
Math.random
Although pure sounds better than impure, impurity is necessary for interacting with an impure world, for example, with a user. You cannot build a functional web site with only pure functions. If you were to use only pure functions you could not:
manipulate the DOM,
fetch data from a server, or
send data to a server.
Thus, both function types play an important role in web development. Yet separating functionality to pure business logic and impure functionality (data fetching, DOM manipulation etc.) might be a good idea. This way you could ensure that your business logic is easily testable and re-factorable. Business logic mixed with side-effects make modifying applications much more demanding.
Tips:
Once a function is tested, remove
console.logs
Do not directly mutate data passed to your function. Create a copy, modify it and return the modified copy.
Do not access any values outside of your function
Always
return
what was modified inside functionSeparate side-effects to their own functions
Higher-order functions: Functions
in JavaScript are objects
.
What this means in practice is that you can assign a function to
a variable, to array elements, or to an object. In addition, as an object,
function has its own prototype property with a value Function.prototype
.
Function.prototype defines three functions: call()
, bind()
, apply()
.
const greet = (name) => console.log(`Greetings, ${name}`);
greet("Teemu Teekkari"); // Greetings, Teemu Teekkari
In the example above, we pass a string as a parameter to the function greet
.
Instead of a string, we could also pass a function as a parameter.
const sayHi = () => console.log("Hi!");
const sayHello = () => console.log("Hello");
const greet = (type, sayHi, sayHello) => type === "Hi" ? sayHi() : sayHello();
greet("Hi", sayHi, sayHello); // Hi!
greet("Hello", sayHi, sayHello); // Hello
Here, we could say that the function greet()
is a higher-order function,
because it takes functions as parameters. Passing functions as parameters implies
also that functions are first-class citizens
in the language.
[The syntax ? :
used in the previous example is called
ternary operator.
It’s basically a shorter version of if-else
.]
Attention
When passing functions as parameters you are not calling the function and therefore you omit the parentheses (and possible arguments) and only pass the name of the function. Calling the function actually returns what the function returns and then passes that return value to the higher-order function.
const sayHi = () => console.log("Hi!");
const sayHello = () => console.log("Hello");
const greet = (type, sayHi, sayHello) => type === "Hi" ? sayHi() : sayHello();
greet("Hi", sayHi, sayHello); // pass in functions
greet("Hello", sayHi(), sayHello()); // pass in results of the functions "Hi!" and "Hello"
We could also have a higher-order function that would return a function:
const printGreetings = () => console.log("Greetings");
const getGreetingsFunction = () => printGreetings;
const greet = getGreetingsFunction();
greet(); // Greetings
Function composition: Function composition is used in combining multiple simple functions into a more complex one. The result of each function is passed as a parameter to the next one. We define two functions: the first function adds 2 to the parameter, and the other one multiplies by it 2.
const add2 = (n) => n + 2;
const times2 = (n) => n * 2;
Now these functions can be used and re-used in multiple places and one could even apply the functions in different order or multiple times to produce different end results.
console.log(add2(times2(5))); // 12
console.log(times2(add2(5))); // 14
console.log(add2(times2(add2(5)))); // 16
console.log(times2(add2(times2(5)))); // 24
What if we had to write something like this?
const accessRights = a(b(c(d(e(f(g("Teemu Teekkari")))))));
Pure functions enable function compositions to be as long as we wish, provided that output of the previous function is the anticipated input of the next function in a chain.
We can have a helper function compose()
, too. With compose()
, we can write
compositions in another form. (Note: that in this case the order of evaluation is
from left-to-right compared with the right-to-left of the previous example):
const compose = (...fns) => (initialVal) => fns.reduceRight((val, fn) => fn(val), initialVal);
const accessRightsFn = compose(a, b, c, d, e, f, g);
const accessRights = accessRightsFn("Teemu Teekkari");
Tips:
In real scenario you would not write the compose function yourself but use a library like Lodash.
Exercises¶
In the following short exercises, the goal is to differentiate pure functions from impure.