April 17, 2018
as the name “functional programming” suggests, this lesson has a heavy focus on functions. In JavaScript you can describe a function in several ways:
function
keyword:function func(params){ ...doSomething... return result }
const methodHolder = { func(params){ ...doSomething... return result } }
const func = function(params) { ...doSomething... return result }
const func = (params) => { ...doSomething... return result }
Arrow functions have some benefits over the classical function/method declaration.
this
context gets set at the time of definitionthis
is weird in JS:In the programming model of JavaScript it is totally fine to
redefine the owner of a function when you call it, so
whenever you use this
, you have to make sure that the
context is set correctly when you call the function.
A short example:
var person = { name: "Clara", greet() { return "Hello " + this.name; } }; person.greet(); // -> 'Hello Clara' var greet = person.greet; greet(); // -> 'Hello ' greet.call({ name: "Peter" }); // -> 'Hello Peter' greet.call({ noPerson: 500 }); // -> 'Hello undefined'
To solve this, like always, you have several possible ways to go.
var person = { name: "Clara", greet() { return "Hello " + person.name; } }; person.greet(); // -> 'Hello Clara' var greet = person.greet; greet(); // -> 'Hello Clara' greet.call({ name: "Peter" }); // -> 'Hello Clara' greet.call({ noPerson: 500 }); // -> 'Hello Clara'
class Person { constructor(name) { this.name = name; this.greet = () => "Hello " + this.name; } } var person = new Person("Lotte"); person.greet(); // -> 'Hello Lotte' var greet = person.greet; greet(); // -> 'Hello Lotte' greet.call({ name: "Peter" }); // -> 'Hello Lotte' greet.call({ noPerson: 500 }); // -> 'Hello Lotte'
const greet = name => "Hello " + name; greet("Bert"); // -> 'Hello Bert'
The lesson-learned should be, only use this
if you really
have to, when possible use pure functions.
Now back to functional programming.
One fundamental building block is the usage of
“pure” functions,
this definition is close to the mathematical definition of
functions f(x) -> y
. A pure function returns a value
purely defined by the parameters passed into it, and there
should be no dependencies to variables defined outside of
the function’s scope. The returned value is also always the
same if the passed parameters are the same (no randomness or
hold state in the function). Besides calculating the
resulting value the function has no other effects. So never
ever mutate the incoming values.
function f(x, y) { return x + y; }
let count = 0; function f(x, y) { count++; return x + y; }
const f = (x, y) => x + y;
const y = 2; const f = x => x + y;
const f = x => x + 2;
const f = (x, y) => { console.log(x, y); return x + y; };
Another very important paradigm in functional programming is “declarative programming”. Instead of describing the control flow of the program you should care about the logic and behavior. For example use recursion instead of loops.
let a, b, temp; const fibonacci = num => { a = 1; b = 0; while (num >= 0) { temp = a; a = a + b; b = temp; num--; } return b; };
Here is a codesandbox with tests to work on this in the browser:
The parameters passed into a function can be data, like numbers, booleans, strings, objects… and also functions. So you can call a function with a function. Even the returned value can be a function.
Functions taking other functions as arguments or returning functions are called “higher-order-functions”
Getting back to the greeting function, this pattern allows you to define a function to return a function to generate a greeting message for a specific name.
const generateGreetingFunction = name => { return () => "Hello " + name; }; const greetDavid = generateGreetingFunction("David"); greetDavid(); // -> 'Hello David'
or in a shorter way:
const generateGreetingFunctionShort = name => () => "Hello " + name;
The most commonly used higher-order-functions in JS are
array-functions like
forEach
,
filter
,
map
and
reduce
.
Those functions take a function as input and call this
function with every element of the array.
Examples:
const array = [1, 2, 3, 4, 5]; array.map(num => num * 2); // -> [2, 4, 6, 8, 10] array.filter(num => num % 2 === 0); // [2, 4] array.reduce( (accumulator, currentValue) => accumulator + currentValue, 0 ); // -> 15
But because normally you don’t develop just for the matter of programming, here is a reason why you should care about all this: It is a powerful tool to handle and transform data. So it is very useful if you are working in any field were you have some larger datasets. In this course we will use it to transform data into UI.
But first some exercises. As a training data set you can use the data from the Star Wars API provided in the code-sandbox.
Here is an example how to get the names from all residents of Tatooine:
const tatooineResidents = people .filter(person => { return planets.some( planet => planet.id === person.homeworld && planet.name === "Tatooine" ); }) .map(person => person.name);
Written by Kalle Ott for opencampus