+3

Functional Programming in ES6

Functional Programming

Functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

Why this paradigm change

Functional languages use a different paradigm than imperative and object oriented languages. They use side effect free functions as a basic building block in the language. This enables lots of things and makes a lot of things more difficult (or in most cases different from what people are used to)

One of the biggest advantages with functional programming is that the order of execution of side effect free functions is not important. For example in erlang this is used to enable concurrency in a very transparent way. And because functions in functional languages behave very similar to mathematical functions it's easy to translate those into functional languages. In some cases this can make code more readable.

Traditionally one of the big disadvantages of functional programming was also the lack of side effects. It's very difficult to write useful software without IO, but IO is hard to implement without side-effects in functions. So most people never got more out of functional programming than calculating a single output from a single input. In modern mixed paradigm languages like F# or scala this is easier.

Lots of modern languages have elements from functional programming languages. C# 3.0 has a lot functional programming features and you can do functional programming in python too. I think the reasons for the popularity of functional programming is mostly because of two reasons. Concurrency is getting a real problem in normal programming because we're getting more and more multiprocessor computers. And the languages are getting more accessible.

Before you read further more...

Watch this Intro to Functional Programming video by Anjana Vakil She explains everything in a much better way than my blog, which is obviously very helpful for beginners. After you watch the talk, you continue the examples provided here.

How to write functional code

  1. Avoid side-effects, Use pure functions : Make sure your functions does not do more than what its supposed to do. Strictly follow the 'single responisbility' principle. As an example, getEvens() function's only responsibility should be filter and return the even numbers, not printing them on the screen. Functions that follow the SRP are known as pure functions.

  2. Stop manual iteration : Rather than manually iterating over an array, use the provided hihger order functions 'Map, Reduce, Filter, Some, Every'

  3. Avoid mutability : Stop mutating your state. Your state should be immutable

Higher Order Functions

We know functions in JavaScript are nothing but objects. Does this mean

  1. functions can be passed around as arguments to other functions? And
  2. can one function return another function?

The answer to both questions is Yes.

Such functions which taking in other functions are arguments and/or return functions are typically called higher order functions.

In the example below, we will define a higher order function forEach. This function takes in two arguments: a list and an action. The first argument list is an array containing containing objects and the second argument action is a function that will be invoked for each item in list.

let forEach = (list, action) => {
 for (let i = 0; i < list.length; i++) {
   action(list[i]);
 }
};

let logItem = item => console.log(item);

let listOfThings = ["soap", "candle", "deer", "wine", "bread"];
let anotherListOfThings = ["grapes", "apples", "beer", "pizza"];

forEach(listOfThings, logItem);

forEach(
 anotherListOfThings,
 (item => console.log(`${item}'s leangth is ${item.length}'`) )
);

Map

Map works exactly how it worked on other languages. It creates a new array with the results of calling a provided function on every element in this array.

Lets discuss it with a simple example, 'double the array numbers'. With our imperative programming minds, we would've written it step by step, like this:

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

for(var i = 0; i < numbers.length; i++) {
   newNumbers[i] = numbers[i] * 2;
}
let numbers = [1, 2, 3, 4]; // let works similar to var

let newNumbers = numbers.map( x => x*2 );

It makes life so much simpler. Rather than telling the computer what to do, you ask it What you want. How the computer processes the data should not be our concern as long as you are getting the correct response (also, it works as fast as imperatively written code).

Now lets imagine, we have another array starks

let starks = [
 {
   name: "Edard",
   isAlive: false,
   companion: null,
   army: 10000
 },
 {
   name: "Rob",
   isAlive: false,
   companion: {name: "Grey Wind", isAlive: false, breed: "Dire Wolf"},
   army: 30000
 },
 {
   name: "Jon",
   isAlive: true,
   companion: {name: "Ghost", isAlive: true, breed: "Dire Wolf"},
   army: 1000
 },
 {
   name: "Bran",
   isAlive: true,
   companion: {name: "Summer", isAlive: false, breed: "Dire Wolf"},
   army: 2
 },
 {
   name: "Arya",
   isAlive: true,
   companion: {name: "Nymeria", isAlive: false, breed: "Alpha Dire Wolf"},
   army: 0
 },
 {
   name: "Tony",
   isAlive: true,
   companion: {name: "Jarvis", isAlive: false},
   army: 5
 }
];

So, if we need the name of all the Starks, we'd write

let names = starks.map( x => x.names);

Easy as that.

One thing to remember, map (and other functions we're about to discuss) DOES NOT mutate the initial array.

Parameters

[...].map(function(element, index, array) {
 ...
}, thisArg);

index: index in each traversal, moving from left to right

array: original array invoking the method

thisArg: (Optional) object that will be referred to as this in callback

Filter

Like map() method, filter works on each element of the array and returns a new array as output only containing the elements that passed the evaluation.

How it works: filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Array elements which do not pass the callback test are simply skipped, and are not included in the new array.

Use it when: You want to remove unwanted elements based on a condition.

Example:

Return the alive Stark members:

starks.filter( x => x.isAlive);

All the starks with alive companion.

starks.filter(x => x.companion && !x.companion.isAlive)

Like map(), filter does not mutate the array. And has a similar structure

[...].filter(function(element, index, array) {
 ...
}, thisArg);

Reduce

Reduce is another useful higher order function , which generally is used to flatten the array it is called from. reduce() applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.

Example, Counmbine all the men fought under stark banner

starks.reduce((armies, stark) => (armies+stark.army), 0);

How reduce works

[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, currentIndex, array) {
 return previousValue + currentValue;
});

Another example (from fun fun function, we have a file containing customer information in form of tab-separated.

Josim Khan	toast-biscuit	80	2
Josim Khan	blender		200	1
Josim Khan	knife		10	4
Sodrul Pasha	salted-biscuit	80	20
Sodrul Pasha	blender		200	1
Sodrul Pasha	heater		1000	2

We need to convert this tsv to a json object.

{
 'Josim Khan': [
   {'name': 'toast-biscuit', 'price': '80', 'quantity': '2'},
   {'name': 'blender', 'price': '200', 'quantity': '1'},
   {'name': 'knife', 'price': '10', 'quantity': '4'},
 ],
 'Sodrul Pasha': [
   {'name': 'salted-biscuit', 'price': '80', 'quantity': '20'},
   {'name': 'blender', 'price': '200', 'quantity': '1'},
   {'name': 'heater', 'price': '1000', 'quantity': '2'},
 ]
}

We can do this by

import fs from 'fs';

let output = fs.readFileSync('data.txt', 'utf8')
 .trim()
 .split('\n')
 .map(line => line.split(`\t`))
 .reduce(
   (customers, line) => {
     customers[line[0]] = customers[line[0]] || [];
     customers[line[0]].push({
       name: line[1],
       price: line[2],
       quantity: line[3]
     })
   }
 )

Some

When called on an array, some() will return a boolean value as output. It runs the provided function on each elements, and will return true if it finds any of the element passing the evaluation.

Parameters

[...].some(function(element, index, array) {
 ...
}, thisArg);

So, if we need to find if any of the starks are alive, we need to called

starks.some( x => x.isAlive )

Every

This is the exact opposite of how the some() function works. It returns a truthy output as long as all the element passes the evaluation.

Parameters

[...].every(function(element, index, array) {
 ...
}, thisArg);

Example, Are the starks dead yet?

starks.every( x => x.isAlive)

Study Materials

Fun Fun Function's awesome tutorial on Functional Programming


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí