function components and function programming

Function programming and function components are a trend in development now. They have been used in the past, but they have not been understood conceptually. This article summarizes the two together and makes a certain comparison.

Function programming is a programming paradigm, and function components are a product of this paradigm.

Function programming

Programming paradigm

Programming paradigm, programming paradigm, or programming paradigm, refers to a typical programming style in software engineering. Common programming paradigms are: function programming, imperative programming, procedural programming, Object Oriented programming, etc.

Programming paradigms provide and determine the programmer’s view of program execution. For example, in Object Oriented programming, programmers consider a program to be a series of interacting objects. Due to different methodologies, Object Oriented programming paradigms are further divided into class-based programming and prototype-based programming, while in function programming, a program will be regarded as a stateless sequence of function calculations.

Programming paradigms are different from programming languages, such as Object Oriented languages. It only means that the language provides more convenient encapsulation, inheritance, and polymorphic syntax. It does not mean that you can only write Object Oriented code, nor does it mean that the code you write conforms to Object Oriented paradigm

There are many more programming paradigms, here is the table of contents of Wiki Lingo:

  • Imperative
    • Procedural
    • Block structure
    • Structured
    • Modularization
  • function
    • First class function
    • Pure function
    • Implicit
    • Pattern matching
    • Derived formula
  • Object Oriented
    • based on class
    • Based on prototypes
    • Contractual
    • Face section
    • Agent oriented
  • Array type
  • Data flow
    • Synchronous
    • Responsive
    • Stream processing
    • Based on process

Function programming is a programming paradigm

Function programming, also known as function programming or functional programming, is a programming paradigm that treats computer operations as function operations and avoids the use of program state and mutable objects.

** In function programming, a function is a first-class object or a first-class function, which means that a function can either be used as an input parameter value for other functions, or return a value from a function **, be modified or assigned to a variable. The lambda calculus is the most important foundation of this paradigm. The function of lambda calculus can accept function as an input parameter and output a return value.

Compared with imperative programming, function programming emphasizes the result of program execution rather than the process of execution, advocating the use of several simple execution units to make the calculation results gradual, and deriving complex operations layer by layer, rather than designing a complex execution process.

What is function programming

These keywords can be extracted from the above definition:

  1. Avoid state changes
  2. function as input and output
  3. Related to λ calculus

Regarding this definition, if you just want to briefly understand the manifestation of function programming, you can take a look阮一峰的这篇博客

If you want a deeper understanding, it is also recommended to read the blog above first. It is very short, but it will help to understand the form of some of the formulas below.

Avoiding state changes and functions as input and output is easier to understand. What is lambda calculus? To understand lambda calculus, it will pull out Turing complete, Turing machine. Next, I will briefly explain to you.

What is an expression

Lambda calculus (lambda-calculus) is a formal system developed from mathematical logic to study how functions are abstracted and defined, how functions are applied, and recursion, using rules for variable binding and substitution. It was first published by mathematician Alonzo Church in the 1930s. As a widely used computational model, the lambda calculus can clearly define what a computable function is, and any computable function can be expressed and evaluated in this form, which can simulate the computational process of a single tape Turing machine. However, the lambda calculus emphasizes the application of transformation rules rather than the specific machines that implement them.

** Lambda calculus is comparable to the most fundamental programming language, it includes a transformation rule (variable replacement) and a way to abstract the definition of function **. Therefore, it is generally recognized as an approach that is closer to software than hardware. It has had a great impact on functional programming languages such as Lisp, ML, and Haskell. In 1936 Church used lambda calculus to give a negation of the Entscheidungsproblem: the proposition that two lambda expressions are equivalent cannot be determined by a “general algorithm”, which is the first problem that undecidability can be proved, even in停机问题Before.

The lambda calculus involves constructing lambda terms and performing reduction operations on lambda terms. In the simplest lambda calculus, only the following rules are used to construct lambda terms:

GrammarNameDescription
xvariableuses characters or strings to represent parameters or mathematical values or to represent logical values
(λ x. M)abstractionA complete function definition (M is a lambda term), in which x in the expression is bound to the variable x.
(M N)Applyto function M on the parameter N. M and N are lambda terms.

Generates an expression such as: (λ x.λ y. (λ z. (λ x.zx) (λ y.zy)) (x y)). If the expression is unambiguous, the parentheses can be omitted. For some applications, logical and mathematical constants and related operations may be included.

** The λ calculus is Turing complete, that is, it is a general model that can be used to simulate any Turing machine **. λ is also used in λ expressions and λ terms to represent binding a variable to a function.

The lambda calculus can be typed or untyped. In typed lambda calculus (which is untyped as described above), the function can only be applied when the parameter types and input types match. Typed lambda calculus is weaker than untyped lambda calculus - the latter is the main part of this entry - because typed lambda operations express less than untyped lambda calculus; at the same time, the former allows more theorems to be proved. For example, in simple typed lambda calculus, the operation can always be stopped, while in untyped lambda calculus this is not necessarily the case (because of the halting problem). One reason there are many typed λ calculus is that they are expected to do more (something that previous typed λ calculus could not do) and at the same time hope to be able to prove more theorems.

The lambda calculus has many applications in mathematics, philosophy, linguistics, and computer science. It occupies an important position in programming language theory, and function programming implements lambda calculus support. Lambda calculus is also a research hotspot in category theory.

The lambda formula has three main points:

  • Binding relationship. Variables are arbitrary, x, y, and z are fine, it is just a proxy for specific data.
  • recursion definition. The λ term recursion definition, M can be a λ term.
  • Substitution reduction. The λ term can be applied, and the space-separated representation applies N to M, which can be a λ term.

By substitution and reduction, we can treat our calculus like a simplification equation.

For example, we just said ’ (λ x.λ y. (λ z. (λ x.zx) (λ y.zy)) (x y)) ‘, first ’ (λ x.zx) ’ means’ f (x) = zx ‘, then’ (λ x.zx) 3 ‘is’ 3z ’

Calculus: Meaning of Variables

In λ calculus, our expression has only one parameter, so how can it achieve binary operations on two numbers? For example, addition a + b requires two parameters.

At this time, we need to treat the function itself as a value. We can save and pass data (or state) by binding a variable to the context and then returning a new function. The bound variable can be referenced from the context when it needs to be actually used.

For example: ‘λ m.λ n.m + n 5 = λ n.5 + n’, the first function call passes in m = 5, returns a new function, this new function takes a parameter n and returns the result of m + n. The context generated in this case, ** is Closure (closure, a common means of state preservation and reference in function programming) **, and we call the variable m the context that is bound to the second function.

In addition to bound variables, λ calculus also supports free variables, such as this y: ‘λ m.λ n.m + n + y’, where y is a variable that is not bound to the parameter position, called a free variable.

Bound variables and free variables are two sources of state for a function, one can be substituted and the other cannot. In actual programs, bound variables are usually implemented as local variables or parameters, and free variables are implemented as global variables or environment variables.

Calculus: Substitution and Reduction

The calculus is divided into alpha substitution and beta reduction. We actually covered these two concepts in the previous chapter, so let’s introduce them below.

Alpha substitution means that the name of the variable is not important, you can write λ m.λ n.m + n, or λ x.λ y.x + y, which represent the same function during the calculation process. That is to say, we only care about the form of the calculation, not the details of what variables to use to implement it. This is convenient for us to modify the variable name without changing the operation result, so as to facilitate simplification operations when the function is more complex. In fact, even the name of the entire lambda calculus is not important, we only need this form of calculation, not the naming of this form.

Beta reduction means that if you have a function application (function call), then you can substitution the part of the function body that corresponds to the identifier by using parameters (possibly another expression) to replace the identifier. It sounds a bit confusing, but it is actually parameter substitution for a function call. For example: ’ (λm.λn.m + n) 1 3 = (λn.1 + n) 3 = 1 + 3 = 4’

You can replace m with 1 and n with 3, then the whole expression can be reduced to 4. This is also in function programming引用透明性The origin of. It should be noted that 1 and 3 here represent the operation values of the expression, which can be replaced by other expressions. For example, replacing 1 with (λ m.λ n.m + n 1 3) requires two reductions

Lambda expressions in JavaScript: Arrow functions

The ECMAScript 2015 specification introduces arrow functions, which have no this and no arguments. Can only be used as an expression (expression) but not as a statement (statement), the expression produces an arrow function reference, which still has the name and length properties, representing the name of the arrow function and the length of the parameters, respectively. An arrow function is a simple expression, and an arrow function can also be called a lambda function, which is written like a lambda expression.

Arrow functions can be used to do some simple operations. The following example compares the use of four arrow functions:

1
2
3
4
Const add_1 = (x, y) = > x + y;//all local variables
Const add_2 = x = > x + y;//y is a global variable
Const add_3 = x = > y = > x + y;//closure concatenation parameter, curried
Const add_4 = b = > a = > a + b;//The parameter name has nothing to do with the expression result

This is the case directly for numbers (primitive data types). If you are doing operations on functions (referring to data types), things become interesting.

1
2
3
4
5
const fn_1 = x => y => x(y);
const fn_2 = f => x => f(x);
const add_1 = (f => f(5))(x => x + 2);
const add_2 = (x => y => x + y)(2)(5);
const add_3 = (x => x + 2)(5);

fn_x type, indicating that we can use function inside function, when function is passed as data, we can apply function to generate higher-order operations. And x = > y = > x (y) can be understood in two ways, one is that x = > y function passes X = > x (y), and the other is that x passes y = > x (y).

add_x type indicates that an expression can be implemented in many different paths.

Function Programming Fundamentals: Meta, Currying, and Point-Free of Functions

Going back to JavaScript itself, we need to explore whether function itself can bring us more things. We have many ways to create functions in JavaScript:

You can use declarations, expressions, arrow functions, new Functions, etc

Although function has so many definitions, the function keyword declares functions with arguments and this keyword, which makes them look more like object methods than functions.

Moreover, most functions defined by functions can also be constructed (such as new Array).

Next we will only study the arrow function, because it is more like a function in the mathematical sense (only performing the calculation process).

  • No arguments and this.
  • Cannot be constructed new.

Element of function

Regardless of how a function is constructed, the function has two fixed pieces of information that can be obtained.

  • name Represents the name of the function to which the current identifier points.
  • length Represents the length of the argument list when defining the function to which the current identifier points.

Mathematically, we define f (x) = x as a unary function and f (x, y) = x + y as a binary function. In JavaScript we can define the element of a function by using its length when defined.

1
2
3
const one = a => a;
const two = (a, b) => a + b;
const three = (a, b, c) => a + b + c;

The significance of defining the meta of a function is that we can classify the function and specify the exact number of parameters a function needs. The meta of a function plays an important role both at compile time (type checking, overloading) and at runtime (exception handling, dynamically generated code).

If I give you a binary function, you know that you need to pass two parameters. For example, + can be seen as a binary function that takes one parameter on the left and one parameter on the right and returns their sum (or string concatenation).

In some other languages, + is indeed implemented by abstract classes, such as trait Add in the Rust language.

But in the λ calculus we saw above, each function has only one element. Why?

There is only one meta function that is convenient for us to perform algebraic operations. The parameter list of λ calculus is divided in the format of λx.λy.λz, and the return value is generally function. If a binary function is called with only one parameter, it returns an “incomplete call function”. Here, three examples are used to explain “incomplete call”.

Curried function: function element dimensionality reduction technology

Currying a function is a technique for reducing the dimensionality of the elements of a function, a term in honor of the mathematician Alonzo Church we mentioned above.

Curried function helps us turn a multivariate function into an incomplete call, and use the magic of Closure to turn the function call into a delayed partial function (incomplete function call) call. This is very useful in scenarios such as function composition and to reuse

Point-Free | No-argument style: higher-order combinations of functions

There is a Point-Free style in function programming. In the Chinese context, point can probably be regarded as a parameter point, corresponding to the function application (Function Apply) in λ calculus, or the function call (Function Call) in JavaScript, so it can be understood that Point-Free refers to a call without parameters.

Let’s take a daily example of converting binary data to octal data.

1
2
var strNums = ['01', '10', '11', '1110'];
strNums.map(x => parseInt(x, 2)).map(x => x.toString(8));

This code runs fine, but in order to handle this transformation, we need to understand the two functions of parseInt (x, 2) and toString (8) (why there are magic numbers 2 and magic numbers 8), and care about the data (function type a - > b) in the shape of each node (care about the flow of data). Is there a way to only care about imported parameters and exported parameters, not about the data flow process?

1
2
3
4
5
6
const toBinary = x => parseInt(x,  2);
const toString0x => x => x.toString(8);
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

var strNums = ['01', '10', '11', '1110'];
strNums.map(pipe(toBinary, toString0x));

Functional component

What is a function component

Function-style components are functions are components, and components are functions. Its characteristics are that there is no internal state, no lifecycle hook function, and no this (components that do not need to be instantiated).

In daily development, we often develop some purely display business components, such as some details page, list interface, etc. They have a common feature:

As long as you pass in the data, I will display it.

There is no need for internal state and no need to process it in the lifecycle hook function.

At this point you can use functional components.

Why use functional components?

Functional components do not require instantiation, are stateless, and have no lifecycle, so rendering performance is better than ordinary components

Functional component structure is simpler and code structure is clearer

Vue2

  1. Functional components need to specify functional in the declaration component.

  2. Function components do not need to be instantiated, so there is no this, this is replaced by the second parameter of the render function.

  3. Functional components have no lifecycle hook function, cannot use calculated properties, watch, etc.

  4. Function-style components cannot expose events externally through $emit, and calling events can only call external incoming events in a context.listeners.click way.

  5. Because function components are not instantiated, when referencing components externally through ref, the actual reference is HTMLElement.

  6. The props of a function component can be declared only in part or not at all. All properties not declared in the props will be automatically implicitly parsed as props, while all undeclared properties of ordinary components are parsed into $attrs and automatically mounted to the component root element (which can be disabled by the inheritAttrs attribute).

It has been repeatedly emphasized above that any component that does not require instantiation, is stateless, has no lifecycle, and has no other configuration items except props can be rewritten as a function component.

Grammar

Template syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<func text="aaaaaaaa" />
</div>
</template>
<script>
import func from '@/components/func.vue';
export default {
components: {
func
}
};
</script>
1
2
3
<template functional>
<p>{{props.text ? props.text : '哈哈'}}</p>
</template>

Note that there is no < script >… section.

JSX syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
export default {
functional: true,
props: {
text: {
type: String
}
},
/**
* Rendering function
* @param {*} h
* @Param {*} context function component without this, props, slots, etc. all hang on the context
*/
render(h, context) {
console.log(context);
const { props } = context
if (props.text) {
return <p>{props.text}</p>
}
Return < p > haha burp </p >
}
}
</script>

Vue3

Vue3 函数式组件

React

React 函数式组件

Reference article:

https://tech.meituan.com/2022/10/13/dive-into-functional-programming-01.html

https://www.ruanyifeng.com/blog/2012/04/functional_programming.html