You’ve probably heard of Functional programming, or even used it for a while.

But can you tell me exactly what it is?

Do a search on the Internet and you can easily find many answers.

  • A programming paradigm juxtaposed with object-oriented programming and Procedural programming.
  • The main feature is that functions are first-class citizens.
  • An example of this emphasis is the decomposition of computational processes into reusable functionsmapMethods andreduceMethod combinationGraphs algorithm.
  • Only pure functions without side effects are qualified functions.

All of this is true, but not enough to answer the deeper question below.

Why do you do that?

That’s the question that this article is going to answer. I’ll help you understand functional programming and learn the basics of writing it in the simplest language.

To be clear, I am not an expert, but a beginner who has only really started learning functional programming in the last two years. Has been struggling to read all kinds of information, determined to write a clear and easy to understand the course. The following is certainly not rigorous, and may even contain errors, but I find explanations like these easiest for beginners to understand.

Also, this article is a long one, so please be patient as you read it. And finally, Udacity’s Front End Engineer Certification Program. Thank you so much for sponsoring this article.

I. Category theory

The origin of functional programming was a branch of mathematics called Category Theory.

The key to understanding functional programming is to understand category theory. It is a very complex mathematics, which holds that all the conceptual systems in the world can be abstracted into categories.

1.1 Concept of category

What is a category?

Wikipedia defines a sentence as follows.

“Categories are objects connected by arrows.” (In mathematics, a category is an algebraic structure that comprises “objects” that are linked by “arrows”.)

That is to say, concepts, things, objects, etc., which have a certain relationship with each other, constitute “categories”. Anything can define a category if it can find a relationship between them.

The points in the figure above, with the arrows between them, form a category.

The arrows, which represent the relationships between the members of a category, are formally known as morphism. Category theory holds that all members of the same category are “transformations” of different states. Through “morphism”, one member can morph into another.

1.2 Mathematical Model

Since a category is all objects that satisfy a certain deformation relation, a mathematical model of it can be concluded.

  • All members are a set
  • The deformation relation is a function

That is to say, category theory is a higher level of set theory abstraction, simple understanding is “set + function”.

Theoretically, from one member of the category, you can calculate all the other members by using functions.

1.3 Categories and containers

We can think of the category as a container containing two things.

  • Value (value)
  • The deformation of the values, that’s the function.

Let’s use code to define a simple category.

class Category { constructor(val) { this.val = val; } addOne(x) { return x + 1; }}Copy the code

In the code above, a Category is a class that is also a container containing a value (this.val) and a deformation relationship (addOne). As you can probably already see, the categories here are all the numbers that are 1 apart from each other.

Note that wherever “containers” are mentioned in the rest of this article, they are all “categories”.

1.4 Relation between category theory and functional programming

Category theory uses functions to express the relationships between categories.

With the development of category theory, a whole set of operation methods of functions have been developed. This method was originally used for mathematical operations, but later someone implemented it on a computer and it became what is known today as functional programming.

At its core, functional programming is just categorical operations, the same kind of thing as mathematical logic, calculus, and determinants, mathematical methods that happen to be used to write programs.

So, do you see why functional programming requires functions to be pure, with no side effects? Because it’s a mathematical operation, and its original purpose is to evaluate, to do nothing else, otherwise it wouldn’t satisfy the functional algorithm.

In short, in functional programming, a function is a pipe. One value goes in here, a new value comes out there, nothing else.

Function synthesis and Currization

Functional programming has two basic operations: composition and keriification.

2.1 Synthesis of functions

If a value passes through multiple functions to become another value, you can combine all the intermediate steps into a single function, which is called “compose of functions.”

In the figure above, the deformation relationship between X and Y is f, and the deformation relationship between Y and Z is g, so the relationship between X and Z is the composition of g and F, g dot f.

Here is the code implementation, I use JavaScript language. Note that all of the sample code in this article is simplified; see the Reference links section for a complete Demo.

The simple code for synthesizing the two functions is as follows.


const compose = function (f, g) {
  return function (x) {
    return f(g(x));
  };
}
Copy the code

The composition of functions must also satisfy the associative law.

Compose (f, g, h) = compose(f, g, h) = compose(f, g, h)Copy the code

Composition is also one reason why functions must be pure. Because how do you compose an impure function with other functions? How do you guarantee that it will behave the way you want it to behave?

As mentioned earlier, functions are like pipes of data. Function composition, then, connects these pipes and lets data pass through them all at once.

2.2 Mr Currie

F (x) and g(x) are combined to form f(g(x)), with the hidden premise that both f and g can accept only one parameter. If you can accept multiple arguments, such as f(x, y) and g(a, b, c), function composition is cumbersome.

That’s where the currization of the function comes in. The so-called “Coriolization” is to transform a function with many parameters into a function with one parameter.

Function add(x, y) {return x + y; } function addX(y) {return function (x) {return x + y; }; } addX(2)(1) // 3Copy the code

With The Currization, we can do that, all functions take one argument. Unless otherwise noted, the default function takes only one argument, which is the value to be processed.

Third, functor

Functions can be used not only to convert values within the same category, but also to convert one category into another. This is where functors come in.

3.1 The concept of functors

Functors are the most important data types in functional programming, as well as the basic units of operation and function.

It is first and foremost a category, that is, a container containing values and deformation relations. In particular, its deformation relationship can be applied to each value in turn, transforming the current container into another container.

In the figure above, the circle on the left is a functor, representing categories of names. The external incoming function, f, will turn to the right-hand side for breakfast.

Here’s a more general picture.

In the figure above, the function f performs the conversion of values (a to B), passing it to the functor, and then implementing the conversion of categories (Fa to Fb).

3.2 Code implementation of functor

Any data structure with map methods can be used as an implementation of functors.

class Functor { constructor(val) { this.val = val; } map(f) { return new Functor(f(this.val)); }}Copy the code

In the above code, Functor is a Functor whose map method takes f as an argument and returns a new Functor containing the value that f(this.val) processed.

By convention, the flag of a functor is that the container hasmapMethods. This method maps every value in a container to another container.

Here are some examples of usage.


(new Functor(2)).map(function (two) {
  return two + 2;
});
// Functor(4)

(new Functor('flamethrowers')).map(function(s) {
  return s.toUpperCase();
});
// Functor('FLAMETHROWERS')

(new Functor('bombs')).map(_.concat(' away')).map(_.prop('length'));
// Functor(10)
Copy the code

The above example shows that all operations in functional programming are performed by functors, that is, not directly on a value, but on the value’s container —- functor. Functors themselves have external interfaces (map methods), through which functions are operators that access the container and cause the value inside the container to be deformed.

Therefore, learning functional programming is really learning the various operations of functors. Since operations can be encapsulated inside functors, different types of functors are derived, and there are as many operations as there are functors. Functional programming becomes using different functors to solve real problems.

4, of method

You may have noticed that the new command was used to generate the new functor above. This is very unlike functional programming, because the new command is the hallmark of object-oriented programming.

As a general convention of functional programming, there is a functorofMethod to generate a new container.

So let’s replace new with the of method.


Functor.of = function(val) {
  return new Functor(val);
};
Copy the code

Then, the previous example can be changed to the following.


Functor.of(2).map(function (two) {
  return two + 2;
});
// Functor(4)
Copy the code

This is more like functional programming.

5. Maybe functor

Functors accept various functions and handle values inside the container. The problem here is that the value inside the container may be a null value (such as NULL), and the external function may not have a mechanism to handle null values. If null values are passed in, an error is likely.


Functor.of(null).map(function (s) {
  return s.toUpperCase();
});
// TypeError
Copy the code

In the above code, the value inside the functor is null, resulting in an error when lowercase becomes uppercase.

The Maybe functor is designed to solve this kind of problem. In short, it sets null checking in its map method.

class Maybe extends Functor { map(f) { return this.val ? Maybe.of(f(this.val)) : Maybe.of(null); }}Copy the code

With the Maybe functor, you can’t go wrong with null values.


Maybe.of(null).map(function (s) {
  return s.toUpperCase();
});
// Maybe(null)
Copy the code

A. Either functor b. Either functor

The conditional operation if… Else is one of the most common operations, expressed in functional programming using Either functors.

The Either functor has two internal values: Left and Right. An rvalue is the value used normally, and an lvalue is the default value used when an rvalue does not exist.


class Either extends Functor {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }

  map(f) {
    return this.right ? 
      Either.of(this.left, f(this.right)) :
      Either.of(f(this.left), this.right);
  }
}

Either.of = function (left, right) {
  return new Either(left, right);
};
Copy the code

Here’s how.


var addOne = function (x) {
  return x + 1;
};

Either.of(5, 6).map(addOne);
// Either(5, 7);

Either.of(1, null).map(addOne);
// Either(2, null);
Copy the code

In the above code, an rvalue is used if it has a value, and an lvalue is used otherwise. In this way, Either functors express conditional operations.

A common use of Either functors is to provide default values. Here’s an example.


Either
.of({address: 'xxx'}, currentUser.address)
.map(updateField);
Copy the code

In the code above, if the user does not provide an address, the Either functor uses the default address of the lvalue.

Another use of Either functors is in place of try… Catch, which uses an lvalue to indicate an error.

function parseJSON(json) { try { return Either.of(null, JSON.parse(json)); } catch (e: Error) { return Either.of(e, null); }}Copy the code

In the above code, the lvalue is null, indicating that there is no error, otherwise the lvalue would contain an error object e. In general, any operation that can go wrong can return a Either functor.

Ap functor

Functors contain values that could be functions. We can imagine a situation where the value of one functor is a number and the value of the other functor is a function.


function addTwo(x) {
  return x + 2;
}

const A = Functor.of(2);
const B = Functor.of(addTwo)
Copy the code

In the code above, the value inside functor A is 2, and the value inside functor B is addTwo.

Sometimes we want A function inside of functor B to be able to operate on the values inside of functor A. That’s where the AP functor comes in.

Ap stands for applicative. A functor that has an AP method deployed is an AP functor.

class Ap extends Functor { ap(F) { return Ap.of(this.val(F.val)); }}Copy the code

Note that the argument to the AP method is not a function, but another functor.

Therefore, the previous example can be written in the following form.


Ap.of(addTwo).ap(Function.of(2))
// Ap(4)
Copy the code

The point of ap functors is that functions with multiple arguments can be evaluated from multiple containers to achieve chain operation of functors.


function add(x) {
  return function (y) {
    return x + y;
  };
}

Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
// Ap(5)
Copy the code

In the above code, the add function is the Currie form and takes two arguments. With the AP functor, we can evaluate from both containers. There’s another way to write it.


Ap.of(add(2)).ap(Maybe.of(3));
Copy the code

Monad functor

A functor is a container that can contain any value. It is perfectly legal to have a functor within a functor. However, this would result in multiple layers of nested functors.


Maybe.of(
  Maybe.of(
    Maybe.of({name: 'Mulburry', number: 8402})
  )
)
Copy the code

The above functor, there are three Maybe nested. To retrieve the inner value, we call this.val three times in a row. This is of course inconvenient, hence the Monad functor.

The purpose of Monad functors is to always return a single layer functor. It has a flatMap method, which does the same thing as the Map method, except that if a nested functor is generated, it fetches the values inside the nested functor, ensuring that it always returns a single layer container without nesting.

class Monad extends Functor { join() { return this.val; } flatMap(f) { return this.map(f).join(); }}Copy the code

In the above code, if f returns a functor, this.map(f) generates a nested functor. Therefore, the Join method ensures that the flatMap method always returns a single-layer functor. This means that nested functors are flatten.

9. IO operations

An important application of Monad functors is to implement I/O (input/output) operations.

I/O is not pure operation, ordinary functional programming can not do, then need to write IO operation Monad functor, through it to complete.


var fs = require('fs');

var readFile = function(filename) {
  return new IO(function() {
    return fs.readFileSync(filename, 'utf-8');
  });
};

var print = function(x) {
  return new IO(function() {
    console.log(x);
    return x;
  });
}
Copy the code

In the above code, reading a file and printing are themselves impure operations, but readFile and print are pure functions because they always return IO functors.

If the IO functor were a Monad with the flatMap method, we could call both functions as follows.


readFile('./user.txt')
.flatMap(print)
Copy the code

That’s the magic. The above code does the impure thing, but since the flatMap returns an IO functor, the expression is pure. We do the side effects with a pure expression, and that’s what Monad does.

Since the return is also an IO functor, chain operations can be implemented. Therefore, in most libraries, the flatMap method is renamed chain.

var tail = function(x) { return new IO(function() { return x[x.length - 1]; }); } readFile('./user.txt').flatmap (tail).flatmap (print) readFile('./user.txt').chain(tail).chain(print)Copy the code

The above code reads the file user.txt and then selects the last line to output.

10. Reference links

  • JS functional programming guide
  • Taking Things Out of Context: Functors in JavaScript
  • Functor.js
  • Maybe, Either & Try Functors in ES6
  • Why Category Theory Matters

(End of text)

= = = = = = = = = = = = = = = = = = = = = = = = = = = =

Thank you for reading the full text. Here’s another promotion, take another minute to read it.

Last October, I told you about Udacity, a technology learning platform from Silicon Valley, and their nanodegree.

Now, as they approach the anniversary of their entry into the Chinese market, another localization course has been released. The “Front-end Developer” certification program is a collaboration between Google and Github.

This course is an international standard, with easy to understand explanations, rich examples, close to the development practices of large companies and help you get a firm grip on the most practical front-end technologies.

The course is taught in English by Silicon Valley engineers with full Chinese subtitles, as well as all-Chinese study tutorials, as well as a synchronous study group and tutor supervision service introduced for the first time in China, including one-to-one coding tutorials. After passing the course, you can also get a study certificate issued by Google and Github.

Registration for the course opens today (22 February), so click here to find out more. When my readers sign up, please use the promo code ruanyfFEND.

Finally, please scan the code and follow youdaxue to track the latest information of IT online learning and training.

(after)