4
头图

0. Introduction

I've always had a keen interest in writing better code. If you can really understand what is abstract and what is concrete, you can understand why interfaces and function types are so common in modern programming languages. After programming in a functional language, it becomes clear why, over time, more mainstream languages began to adopt some of the features of functional languages that were taken for granted.

I wrote this article by taking my years of learning about type systems and programming language development together, distilling them, and supplementing them with real-world applications. The context of this article is as follows:

  1. Overview: What are types? Why introduce the concept of types?
  2. Basic Types in Programming Languages
  3. type combination
  4. OOP and interface types
  5. function type
  6. Functors and Monads

1. Overview: What are types? Why introduce the concept of types?

There is a direct link between the theory of type system design and the day-to-day production of software. This is not a revolutionary discovery: complex type system properties exist to solve real-world problems.

This section introduces types and the type system, discusses why they exist and why they are useful. We'll discuss types in the type system and explain type strength, static typing, and dynamic typing.

Two terms: type, type system

type

A type is a classification of data that defines the operations that can be performed on the data, the meaning of the data, and the set of values that the data is allowed to accept. The compiler and runtime check types to ensure data integrity, enforce access restrictions, and interpret data as the developer intended.

type system

A type system is a set of rules that assign and enforce types for elements of a programming language. These elements can be variables, functions, and other high-level structures. The type system assigns types in two ways: the programmer specifies the type in the code, or the type system implicitly infers the type of an element based on the context. The type system allows certain conversions between types and prevents other types of conversions.

Start with the constraints of a complex system

The word "system" has been around for a long time, in ancient Greece it refers to the totality of complex things. In modern times, some scientists and philosophers have often used the term system to refer to a complex whole with a certain structure. In the macroscopic world and the microscopic world, from elementary particles to the universe, from cells to human society, from animals and plants to social organizations, everything is a systematic way of existence.

Cybernetics (Wiener, 1948, "Cybernetics (or the Science of Control and Communication in Animals and Machines)") tells us that negative feedback is the mechanism for system stability, the reason why an organizational system can be quickly eliminated after being disturbed The key to the ability of the deviation to return to constant lies in the existence of a "negative feedback regulation" mechanism: the system must have a means of measuring the difference between the disturbed variable and the constant value necessary to maintain the organism's survival. For example, the constraints of a real-time system complexity task include time constraints, resource constraints, execution order constraints, and performance constraints.

Type checking: Type checking ensures that the program follows the rules of the type system. The compiler does type checking when transpiling code, and the runtime does type checking when executing code. The component of the compiler responsible for enforcing type rules is called the type checker. If the type check fails, it means that the program does not obey the rules of the type system, and the program will fail to compile, or a runtime error will occur. "A program that obeys the rules of the type system is equivalent to a logical proof."

Type systems are the "negative feedback regulators" of complex software systems. Through a set of type specifications, plus compilation monitoring and testing mechanisms, the data abstraction of software systems and the security of data processing at runtime are realized.

As software becomes more and more complex, we increasingly need to ensure that software works correctly. Through monitoring and testing, it can be shown that, given specific inputs, the behavior of the software at a specific moment is compliant. But types give us a more general proof that code will behave as specified, no matter what input is given.

For example, marking a value as const, or marking a member variable as private, type checking will enforce restrictions on enforcing many other security properties.

From 01 to real world object model

Types give meaning to data. Types also limit the set of valid values a variable can accept.

At the low-level hardware and machine code levels, program logic (code) and the data it operates on are represented in bits. At this level, there is no distinction between code and data, so errors are prone to occur when systems mistake code for data, or data for code. These errors can crash the system or lead to serious security flaws that attackers exploit to allow the system to execute their input data as code.

Through the study of programming languages, people are designing more and more powerful type systems (for example, the type systems of Elm or Idris languages). Haskell is becoming more and more popular. At the same time, work is progressing to add compile-time type checking to dynamically typed languages: Python adds support for type hints, while TypeScript is a language created purely to add compile-time type checking to JavaScript.

Obviously, there is value in adding types to your code, and you can write better and safer code by taking advantage of the features of the type system provided by programming languages.

data types in programming languages

Type systems are a fundamental concept that every programming language has.

  1. Lisp data types can be classified as:
  2. Scalar types - For example, numeric types, characters, symbols, etc.
    - Data structures - eg, lists, vectors, bit vectors and strings.
  3. The type system of the C language is divided into basic types and composite types. The basic types can be subdivided into: integer value types and floating-point value types, and the memory lengths occupied by different types are different:

Integer Numeric Primitive Type

char occupies one byte
short occupies two bytes
int is currently basically 4 bytes
long int (can be abbreviated as long) (4 bytes for 32-bit systems, 8 bytes for 64-bit systems)
long long int (can be abbreviated as long long) takes 8 bytes

Floating point numeric primitive type

float occupies 4 bytes (single precision)
double occupies 8 bytes (double precision floating point number)

Composite types include the following

struct structure
union union
enum enumeration (length equal to int)
array pointer
  1. There are rich data types in the Go language. In addition to the basic integer, floating-point, boolean, and string types, there are also arrays, slices, structs, interfaces, and funcs. , map , channel (channel), etc.
  2. Integer type: int8 int6 int32 int64; corresponding unsigned integer type: uint8 uint16 uint32 uint64. uint8 is the well-known byte type, int16 corresponds to the short type in C language, and int64 corresponds to the long type in C language.
  3. Floating point type: float32 and float64, the two floating point data formats of floating point follow the IEEE 754 standard.
  4. Slices: mutable arrays, an abstraction of arrays. Slices are reference types.
  5. Interface: Implement polymorphism and interface-oriented programming. Define an interface I, and then use different structures to implement the interface I, and then use the interface object as a formal parameter to pass in different types of objects and call related functions to achieve polymorphism. Interfaces can be implemented nested, including small interfaces through large interfaces.

Type strength

There is no authoritative definition of the difference between strong and weak typing. Most of the early discussions of strong and weak typing can be summed up as the difference between static and dynamic typing.

But the popular saying is that strong types tend to not tolerate implicit type conversions, while weak types tend to tolerate implicit type conversions. In this way, a strongly typed language is usually type-safe, that is, it can only access the memory it is authorized to access in permitted ways.

Typically, dynamically typed languages tend to be associated with interpreted languages such as Python, Ruby, Perl or Javascript, while statically typed languages tend to be associated with compiled languages such as Golang, Java or C.

I summarize a classification diagram of common programming language types, note that the four areas of the split are partitions, for example, PHP and JS are both dynamically weakly typed.

static typing vs dynamic typing

We often hear the question "static vs dynamic typing", but the difference between the two is when the type checking happens.

  1. The static type system determines the types of all variables at compile time and throws exceptions if they are used incorrectly. A static type system, which converts runtime errors into compile-time errors, can make code easier to maintain and more adaptable, especially for large applications.
  2. Whereas in dynamic typing, the type is bound to the value. Checking is done at runtime. The dynamic type system determines variable types at runtime and throws an exception if there is an error, which can crash the program if not handled properly. Dynamic typing does not impose any type constraints at compile time. Dynamic typing is sometimes called "duck typing" in everyday communication, and the name comes from the saying: "If an animal walks like a duck and quacks like a duck, then it's a duck." A variable needs to be used freely, and the runtime will apply a type to the variable.

The early type error reporting of the static type system guarantees the safety of large-scale application development, while the disadvantage of the dynamic type system is that there is no type checking at compile time, and the program is not safe enough. The robustness of the code can only be guaranteed by extensive unit testing. But programs that use a dynamic type system are easy to write and don't take a lot of time to make sure they're typed correctly. The so-called "you can't have both" is a philosophical question about "efficiency" and "quality".

However, modern type checkers have powerful type inference algorithms that allow them to determine the type of a variable or function without requiring us to write out the type explicitly.

summary

  • A type is a data classification that defines the operations that can be performed on such data, what it means, and the set of allowed values.
  • A type system is a set of rules that assign and enforce types for elements of a programming language.
  • Types limit the range of values a variable can take, so in some cases run-time errors are translated into compile-time errors.
  • Immutability is a data property imposed by a type that guarantees that the value won't change when it shouldn't.
  • Visibility is another type-level property that determines which components can access which data.
  • Type identifiers make it easier for people who read the code to understand the code.
  • Dynamic typing (or "duck typing") determines the type at runtime.
  • Static typing checks the type at compile time, catching type errors that might otherwise be runtime errors.
  • The strength of a type system is a measure of how many implicit conversions the system allows between types.
  • Modern type checkers have powerful type inference algorithms that allow them to determine the type of a variable or function without requiring us to write out the type explicitly.

2. Basic types in programming languages

This section introduces the features of programming language type systems, starting with basic types, and continuing through functional types, OOP, generic programming, and higher-order types such as functors and monads.

basic type

Commonly used basic types include null types, cell types, boolean types, numeric types, string types, array types, and reference types.

function type

"Functional types are another stage in the evolution of the type system based on primitive types and their combinations."

Most modern programming languages support anonymous functions, also known as lambdas. A lambda is like a normal function, but without a name. lambdas are used whenever we need to use a one-off function. The so-called one-time function means that we will only refer to this function once, so naming it becomes redundant work.

lambda or anonymous function: A lambda, also known as an anonymous function, is a function definition without a name. Lambdas are typically used for one-off, short-lived processing and are passed around like data.

Functions can accept other functions as arguments, or return other functions. "Standard" functions that take one or more non-function arguments and return a non-function type are also called first-order functions, or ordinary functions. A function that accepts a first-order function as an argument or returns a first-order function is called a second-order function.

We could go further and call a function that accepts a second-order function as an argument or that returns a second-order function a third-order function, but in practice, we simply call all functions that accept or return other functions higher-order function.

We can simplify the strategy pattern using "function types". If a variable is a function type (named function type), and functions can be used where values of other types are used, it is possible to simplify the implementation of some common constructs and abstract common algorithms into library functions.

generic programming

Generic programming supports strong decoupling and code reuse.
Generic data structures separate the layout of the data from the data itself. Iterators support traversing these data structures. Generic algorithms (eg, the most classic sort algorithm) are algorithms that can be reused on different data types. Iterators are used as an interface between data structures and algorithms, and can enable different algorithms based on the iterator's capabilities.

For example, a generic function:

 (value:T) => T

Its type parameter is T. Concrete functions are created when an actual type is specified for T. An example of a specific class diagram is as follows:

Another example is a generic binary tree.

The code and schematic diagram of the generic higher-order functions map() , filter() , reduce() are as follows.

  • map()
 public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

  • filter()

     public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
      return filterTo(ArrayList<T>(), predicate)
    }
  • reduce()

     public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
      val iterator = this.iterator()
      if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
      var accumulator: S = iterator.next()
      while (iterator.hasNext()) {
          accumulator = operation(accumulator, iterator.next())
      }
      return accumulator
    }

Higher order types

Higher-order types are similar to higher-order functions, representing a type parameter with another type parameter. For example, T<U> or Box<T<U>> has a type parameter T, which in turn has a type parameter U.

Just as higher-order functions are functions that accept other functions as arguments, higher-order types are those that accept other kinds as arguments (parameterized type constructors).

type constructor

In the type system, we can think of a type constructor as a function that returns a type. We don't need to implement type constructors ourselves because that's how the type system sees types internally.

Every type has a constructor. Some constructors are simple. For example, you can think of a constructor of type number as a function that takes no arguments and returns a type of number, that is, () -> [number type].

For generics, the situation has changed. Generic types, such as T[], require an actual type parameter to generate a concrete type. Its type constructor is (T) -> [T[] type]. For example, when T is number, the type we get is a numeric array number[], and when T is string, the type we get is a string array string[]. Such constructors are also called "kinds", the kinds of types T[].

Higher-order types, like higher-order functions, bring a level of abstraction up. Here, our type constructor can accept another type constructor as an argument.

Null type (nil / null pointer)

null vs billion dollar bug

Renowned computer scientist and Turing Award winner Sir Tony Hall has called the null reference a "billion dollar mistake" he made. He said:
"I invented the null reference in 1965. Now I call it the billion-dollar mistake I made. At the time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to make The compiler automatically performs checks to ensure that all references are absolutely safe. However, I couldn't resist the temptation to add null references to the type system simply because it was too easy to implement. This made it difficult to Counting errors, bugs, and system crashes may have cost hundreds of millions of dollars over the past four decades."
There have been a lot of null dereference bugs over the decades, so now it's clear that it's best not to let null (i.e. no value) itself be a valid value for a type.

Next, we describe the various ways in which new types can be created by combining existing types.

3. Type combinations

This section describes type composition, the various ways in which types are combined to define new types.
Composite types are types that are put together so that the value of the resulting type consists of the values of each member type.

Algebraic Data Type (ADT)

ADT is a way of combining types in a type system. ADT provides two ways to combine types:

  1. product type
  2. and type

product type

Product types are what we call composite types in this chapter. Tuples and records are product types because their value is the product of the constituent types. When type A = {a1, a2} (possible values for type A are a1 and a2) and B = {b1, b2} (possible values for type B are b1 and b2) combined into element type <A, B>, the result is A×B = {(a1, b1), (a1, b2), (a2, b1), (a2, b2)}.

Tuple and record types are both examples of product types. Additionally, records allow us to assign meaningful names to each member.

and type

A sum type is a combination of several other types into a new type that stores the value of any one of the constituent types. The sum type of types A, B, and C can be written A + B + C, which contains a value of A, or a value of B, or a value of C.

Optional types and variant types are examples of "sum types".

4. OOP and interface types

This section introduces the key elements of object-oriented programming, when to use each, and discusses interfaces, inheritance, composition, and mixins.

OOP: Object Oriented Programming

Object-Oriented Programming (OOP): OOP is a programming paradigm based on the concept of objects, which can contain data and code. Data is the state of an object, and code is one or more methods, also called "messages". In object-oriented systems, objects can "talk" or send messages between objects by using the methods of other objects.

Two key characteristics of OOP are encapsulation and inheritance. Encapsulation allows hiding data and methods, whereas inheritance extends a type with additional data and code.

Encapsulation occurs at multiple levels, for example, a service exposes its API as an interface, a module exports its interface and hides implementation details, a class exposes only public members, and so on. As with nested dolls, the weaker the relationship between the two parts of the code, the less information is shared. In this way, the guarantees that the component can make about the data it manages internally is strengthened, because outside code cannot modify this data without going through the component's interface.

An example of an object-oriented inheritance hierarchy for "parameterized expressions". The class diagram is as follows.

The expression here can be calculated by the eval() method to obtain a number. The binary expression has two operands. The addition and multiplication expressions calculate the result by adding or multiplying the operands.

We can model an expression as an IExpression interface with an eval() method. It can be modeled as an interface because it doesn't hold any state.

Next, we implement a BinaryExpression abstract class in which to store the two operands. However, we made eval() an abstract method, thus requiring derived classes to implement the method. Both SumExpression and MulExpression inherit two operands from BinaryExpression and provide their own eval() implementations. code show as below.

Interface Types: Abstract Classes and Interfaces

We use interfaces to specify contracts. Interfaces can be extended and combined.

Interface or contract: An interface (or contract) describes a set of messages understood by any object that implements the interface. Messages are methods, including name, arguments, and return type. Interfaces do not have any state. Like real-world contracts (which are written agreements), interfaces are also equivalent to written agreements, specifying what implementers will provide.

Interface is also called dynamic data type. When the interface is used, the dynamic type of the interface pair position will be changed to the pointed type, and the dynamic value will be changed to the structure of the pointed type.

5. Function Type

This section introduces function types and what we can do when we gain the ability to create function variables. It also shows different ways to implement strategy patterns and state machines, and introduces the basic map(), filter(), and reduce() algorithms. .

What is a function type?

function type or signature

The set of arguments of a function plus the return type is called the function type (or function signature).

Function types are essentially the same as interface types. They are a set of mapping rules (interface protocols) and do not bind specific implementations (class, struct).

The argument types and return types of a function determine the type of the function. Two functions have the same type if they accept the same arguments and return the same type. The set of arguments plus the return type is also known as the function's signature.

first-class function

Assigning functions to variables and treating them like any other value in the type system results in so-called first-class functions. This means that the language treats functions as "first-class citizens", giving them the same rights as other values: they have types, can be assigned to variables, can be passed as arguments, can be checked for validity, and where compatible can be converted to other types.

"First-class functional" programming languages, functions can be assigned to variables, passed as arguments, and used like any other value, making the code more expressive.

A simple strategy pattern

Strategy Design Patterns

The Strategy pattern is one of the most commonly used design patterns. The strategy design pattern is a behavioral software design pattern that allows an algorithm to be selected from a set of algorithms at runtime. It decouples the algorithm from the components that use the algorithm, thereby increasing the flexibility of the overall system. The figure below shows this pattern.

The strategy pattern consists of the IStrategy interface, the ConcreteStrategy1 and ConcreteStrategy2 implementations, and the Context that uses the algorithm through the IStrategy interface. code show as below:

functional strategy

We can define WashingStrategy as a type that represents a function that takes Car as an argument and returns void. Then, we can implement the two car wash services as two functions, standardWash() and premiumWash(), which both accept Car as an argument and return void. CarWash can choose one of these functions to apply to a given car, as shown below.

The strategy pattern consists of Context, which uses one of two functions: concreteStrategy1() or concreteStrategy2(). code show as below:

A simple decorator pattern

The Decorator pattern is a simple behavioral software design pattern that extends the behavior of an object without having to modify the object's class. The decorated object can perform functions not provided by its original implementation. The decorator pattern is shown in the figure.

Figure description: Decorator pattern, an IComponent interface, a concrete implementation, ConcreteComponent, and a Decorator that enhances IComponent with additional behavior.

A decorator for singleton logic

An example of decorator code for singleton logic is as follows.

Implemented with function decorators

Let's implement the decorator pattern using function types.
First, remove the IWidgetFactory interface and use a function type instead. This type of function takes no arguments and returns a Widget:() => Widget.

Where I used IWidgetFactory and passed in an instance of WidgetFactor, now you need to use a function of type () => Widget and pass in makeWidget(). The code is as follows.

We use a technique similar to the strategy pattern above: take a function as an argument and call it when needed. However, each call to use10Widgets() above constructs a new Widget instance.

Next, let's see how to add singleton behavior. We provide a new function singletonDecorator() that takes a function of type WidgetFactory and returns another function of type WidgetFactory. code show as below.

Now, instead of constructing 10 Widget objects, use10Widgets() will call the lambda, reusing the same Widget instance for all calls.

summary

Like the strategy pattern, the object-oriented approach and the functional approach implement the same decorator pattern.

The object-oriented version requires declaring an interface ( IWidgetFactory ), at least one implementation of that interface ( WidgetFactory ), and a decorator class that handles the additional behavior.

In contrast, a functional implementation simply declares the type of the factory function (() => Widget) and uses two functions: a factory function (makeWidget()) and a decorator function (singletonDecorator()).

6. Functor and Monad

Overview

The concepts of functors and monads come from category theory. Category theory is a branch of mathematics that studies structures made up of objects and the arrows between those objects. With these little building blocks, we can build structures like functors and monads. We won't go into the details, just briefly. Many fields (such as set theory, and even type systems) can be expressed in category theory.

Functor

"Talk is cheap, show me the code".

A functor is a data type Functor, which has an attribute value value and a map method. The map method can process the value and generate a new Functor instance. The code for the functor is as follows:

 class Functor<T> {
    private value:T;

    constructor(val:T){
        this.value = val
    }

    public map<U>(fn:(val:T)=>U){
        let rst = fn(this.value)
        return new Functor(rst)
    }
}

Verify that the application instance of Functor is consistent with the data type we want?

 new Functor(3)
    .map(d=>add(d))
    .map(d=>double(d.value))
    .map(d=>square(d.value)) // Functor { value: 256 }

This is a functor, a data type (container) that is constrained by rules and contains a value (value) and a transformation relationship (function map) of the value. It is a new function combination method, which can be called in a chain, can be used to constrain the data structure of transmission, can map the output value of the adaptation function and the input value of the next function, and can avoid the side effects of function execution to a certain extent.

What is the purpose of functors? This problem needs to start from the function composition (Function Composition) mentioned earlier.

Function composition is a way to combine multiple functions into new functions, it solves the problem of function nested calling, and also provides a way to split and combine functions.

functors of functions

In addition to functors, you need to know that there are also functors of functions. Given a function that takes any number of arguments and returns a value of type T.

Functors in Mathematics and Functional Programming

In mathematics, especially category theory, a functor is a mapping between categories (a homomorphism between categories). A functor that maps from a category to itself is called a "self-functor".

In functional programming, functors are the most important data type and the basic unit of operation and function. Functors are container types that implement the map() function and obey some specific rules.

We have a generic type H that contains 0, 1, or more values of some type T, and a function from T to U. In this example, T is a hollow circle and U is a filled circle. The map() functor unpacks T from the H<T> instance, applies the function, and puts the result back into an H<U>.

In fact, the above map(transform: (T) -> R): List<R> higher-order function is a 函子 .

Functor: A functor is a generalization of a function that performs a mapping operation. For any generic type, taking Box<T> as an example, if the map() operation accepts a Box<T> and a function from T to U as arguments, and gets a Box<U>, then the map() is a functor.
Functor Laws
 恒等定律:fmap id = id
组合定律: fmap (g . h) = (fmap g) . (fmap h)

Functors are powerful, but most mainstream languages don't have a good way to express them, because the regular definition of functors relies on the notion of higher-order types (not "higher-order functions", but "higher-order types").

Code Implementation Example of Functor
 class Functor {
  // 构造函数,创建函子对象的时候接收任意类型的值,并把值赋给它的私有属性 _value
  constructor(value) { 
    this._value = value
  }
 
  // 接收一个函数,处理值的变形并返回一个新的函子对象
  map (fn) {
    return new Functor(fn(this._value))
  }
}

let num1 = new Functor(3).map(val => val + 2)

// 输出:Functor { _value: 5 }
console.log(num1)

let num2 = new Functor(3).map(val => val + 2).map(val => val * 2)

// 输出:Functor { _value: 10 }
console.log(num2)

// 改变了值类型
let num3 = new Functor('webpack').map(val => `${val}-cli`).map(val => val.length)

// 输出:Functor { _value: 11 }
console.log(num3)

Monad Functor

The value of a functor supports any data type, and can of course be a functor. But this will cause the problem of functor nesting.

 Maybe.of(3).map(n => Maybe.of(n + 2)) // Maybe { value: Maybe { value: 5 } }

Monads (Monad functors) solve this problem.

Monad Functor always returns a single level functor to avoid nesting. Because it has a flatMap method, if a nested functor is generated, it will take out the value of the latter, ensuring that a single-level functor is returned to avoid nesting.
code show as below.

 class Monad<T> exteds Functor<T>{
    static of<T>(val:T){
        return new Monad(val)
    }

    isNothing() {
        return this.value === null || this.value === undefined
    }

    public map<U>(fn:(val:T)=>U){
        if (this.isNothing()) return Monad.of(null)
        let rst = fn(this.value)
        return Monad.of(rst)
    }

    public join(){
        return this.value
    }

    public flatMap<U>(fn:(val:T)=>U){
        return this.map(fn).join()
    }
}

Monad.of(3).flatMap(val => Monad.of(val + 2)) // Monad { value: 5 }

In general, a Monad functor is a Pointed functor that implements the flatMap method.

Monads consist of the following three parts:

  1. A type constructor (M) that constructs a unary type M<T>.
  2. A type conversion function (return or unit) capable of loading a primitive value into M.

     unit(x) : T -> M T
  3. A combined function bind can take out the value in the M instance, put it into a function fn: T-> M<U> to execute, and finally get a new M instance.

     bind:  执行 fn: T  -> M<U>

Apart from that, it also obeys some rules:

  • Unity rules, usually implemented by the unit function.
  • The associativity rule is usually implemented by the bind function.

Code example:

 class Monad {
  value = "";
  // 构造函数
  constructor(value) {
    this.value = value;
  }
  // unit,把值装入 Monad 构造函数中
  unit(value) {
    this.value = value;
  }
  // bind,把值转换成一个新的 Monad
  bind(fn) {
    return fn(this.value);
  }
}

// 满足 x-> M(x) 格式的函数
function add1(x) {
  return new Monad(x + 1);
}
// 满足 x-> M(x) 格式的函数
function square(x) {
  return new Monad(x * x);
}

// 接下来,我们就能进行链式调用了
const a = new Monad(2)
     .bind(square)
     .bind(add1);
     //...

console.log(a.value === 5); // true

The above code is a basic Monad, which separates multiple steps of the program into a linear flow, processes the data flow through the bind method, and finally gets the result we want.

Functors in Category Theory

Warning: The content below is partial to mathematical theory, and students who are not interested can skip it.

Original: A monad is a monoid in the category of endofunctors (Philip Wadler).
Translation: Monad is a monoid on the category of self-functors ".

Three important concepts are marked here: self functors, categories, and monoids. These are all mathematical knowledge, and we will understand them separately.

What is a category?

Everything is an object, and a large number of objects are combined to form a set. There is one or more connections between objects, and any connection is called a morphism.

An algebraic structure formed by a bunch of objects, and all the morphisms between them, is called a category .

What is a functor?

We call the mapping between categories a functor . A map is a special kind of morphism, so a functor is also a morphism.

What is a self functor?

An autofunctor is a functor that maps a category to itself.

What is a monoid Monoid?

A monoid is a semigroup with identity elements.

What is a semigroup?

A set is a semigroup if it satisfies the associativity law.

What is a unit cell?

An identity element is a special kind of element in a set that is related to binary operations in that set. When the identity element is combined with other elements, those elements are not changed. like:

 任何一个数 + 0 = 这个数本身。 那么 0 就是单位元(加法单位元)
任何一个数 * 1 = 这个数本身。那么 1 就是单位元(乘法单位元)

Ok, we have learned all the technical terms that should be mastered, so let’s briefly explain this explanation:

A monoid on the self functor category can be understood as:

In a set that satisfies the associativity and identity rules, there is a mapping relationship, which can map the elements in the set to the elements of the current set itself.

summary

Without going into category theory, a brief summary of functors and monads.

Functors and monads both provide tools for wrapping input, returning wrapped output.

Functor = unit + map (i.e. tool)

Where,

unit= something that takes raw input and wraps it in a small context.

map= A tool that takes a function as input, applies it to the original value in a wrapper, and returns the wrapped result.

Example: Let's define a function that doubles an integer

// doubleMe :: Int a -> Int b
const doubleMe = a => 2 * a;
Maybe(2).map(doubleMe) // Maybe(4)
Monad = unit + flatMap (or binding or chain)

flatMapmap=As the name suggests, it is a tool to flatten.


Extras: Self-Organization Theory and Complex Software Systems

Self-organization theory is a system theory established and developed in the late 1960s. Its research object is mainly the formation and development mechanism of complex self-organizing systems (life systems, social systems), that is, under certain conditions, how does the system automatically change from disorder to order, from low-level order to high-level order of.

Self-organization is one of the most amazing discoveries of modern nonlinear science and non-equilibrium thermodynamics. Based on in-depth observation and research on the origin of species, biological evolution and social development, some emerging cross-sectional disciplines have defined the concept of self-organization from different perspectives.

From the point of view of system theory, self-organization refers to the process in which a system develops from simple to complex, from rough to meticulous, and continuously improves its own complexity and finesse, driven by the internal mechanism;

From the point of view of thermodynamics, self-organization refers to the process in which a system continuously reduces its own entropy content and improves its order degree by exchanging material, energy and information with the outside world;

From the point of view of statistical mechanics, self-organization refers to the process in which a system spontaneously migrates from the most probable state to the direction with lower probability;

From the point of view of evolution, self-organization refers to the process in which a system continuously improves its organizational structure and operation mode under the action of heredity, mutation and survival of the fittest, thereby continuously improving its adaptability to the environment. The greatest achievement of CR Darwin's theory of biological evolution is to exclude the dominant role of external factors, and for the first time to explain the origin of species and biological evolution from an internal mechanism and a self-organized development process.

What is complex?

"Complexity" is defined as any system whose behavior is difficult to model due to the dependencies, relationships, and interactions between components. More generally, the "whole" of a complex system is greater than the sum of its "parts". That is, you cannot understand a system that behaves as a whole without looking at the individual components and how they interact, and you cannot understand the behavior of a system as a whole by looking only at the individual components and ignoring the effects of the system.

As a software system scales, it becomes large enough that the number of working parts, plus the number of working programmers making changes to it, makes the behavior of the system very hard to reason about.

This complexity is exacerbated by the transition of many organizations to microservice architectures, such as the so-called "Death Star" architecture, where each point on the circumference of a circle represents a microservice and the lines between services represent their interactions.

References


陈光剑
499 声望183 粉丝