Core points
As a programmer, you may want to write elegant, maintainable, scalable, predictable code. The principles of functional programming (FP) can greatly help achieve these goals.
Functional programming is a paradigm or style that emphasizes invariance, first-class functions, citation transparency and pure functions. If you don't understand the meaning of these words, don't worry! We will break down all these terms in this article.
Functional programming originated from λ calculus, a mathematical system that revolves around function abstraction and generalization. Therefore, many functional programming languages seem very mathematical. But the good news is: you don't need to use a functional programming language to apply functional programming principles to your code. In this post, we will use JavaScript, which has many features that make it suitable for functional programming without being limited to that paradigm.
Core principles of functional programming
Now that we have discussed what functional programming is, let's talk about the core principles behind FP.
I like to think of functions as machines - they accept input or parameters and then output something, i.e. return values. Pure functions have no "side effects" or operations that are independent of function output. Some potential side effects include printing values or recording them with console.log
, or manipulating variables outside the function.
This is an example of a non-pure function:
let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();
The following function is a pure function. It accepts input and produces output.
let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();
Pure functions run independently of the state outside the function, so they should not depend on global state or variables outside themselves. In the first example, we use the number
variable created outside the function and set it inside the function. This violates this principle. If you rely heavily on changing global variables, your code will be unpredictable and difficult to track. It will be more difficult to find out where the error occurred and why the value changes. Instead, debugging is easier with only input, output, and function local variables.
In addition, functions should follow reference transparency, which means that given an input, their output will always be the same. In the above example function, if I pass 2 to the function, it will always return 4. This is not the case with API calls or generating random numbers, these are just two examples. Given the same input, the output may or may not be returned.
// 纯函数 function squareNumber(number) { return number * number; } squareNumber(2);
Functional programming also prioritizes invariability, that is, no data is modified directly. Invariance brings predictability – you know the value of the data, and they won’t change. It makes the code simple, testable, and runs on distributed and multithreaded systems.
Invariance often plays a role when we work with data structures. Many array methods in JavaScript directly modify arrays. For example, .pop()
deletes an item directly from the end of the array, while .splice()
allows you to get a part of the array. Instead, in the functional paradigm, we will copy the array and delete the elements we want to eliminate in the process.
// 不具有引用透明性 Math.random(); // 0.1406399143589343 Math.random(); // 0.26768924082159495
// 我们直接修改 myArr const myArr = [1, 2, 3]; myArr.pop(); // [1, 2]
In functional programming, our functions are first-class functions, which means we can use them like any other value. We can create arrays of functions, pass them as arguments to other functions, and store them in variables.
// 我们复制数组而不包含最后一个元素,并将其存储到变量中 let myArr = [1, 2, 3]; let myNewArr = myArr.slice(0, 2); // [1, 2] console.log(myArr);
A higher-order functions are functions that perform one of two operations: they either take the function as one or more of its parameters or return the function. JavaScript has built-in many first-class higher-order functions—such as map
, reduce
and filter
, we can use them to interact with arrays.
filter
is used to return a new array from the old array that contains only values that meet the conditions we provide.
let myFunctionArr = [() => 1 + 2, () => console.log("hi"), x => 3 * x]; myFunctionArr[2](2); // 6 const myFunction = anotherFunction => anotherFunction(20); const secondFunction = x => x * 10; myFunction(secondFunction); // 200
map
is used to iterate over items in the array and modify each item according to the provided logic. In the following example, we double each item in the array by passing a function that multiplies our value by 2 to map
.
const myArr = [1, 2, 3, 4, 5]; const evens = myArr.filter(x => x % 2 === 0); // [2, 4]
reduce
allows us to output a single value based on the input array - it is usually used to sum, flatten arrays, or group values in some way.
const myArr = [1, 2, 3, 4, 5]; const doubled = myArr.map(i => i * 2); // [2, 4, 6, 8, 10]
You can also implement these functions yourself! For example, you could create a filter
function like this:
const myArr = [1, 2, 3, 4, 5]; const sum = myArr.reduce((i, runningSum) => i + runningSum); // 15
The second type of higher-order functions (functions that return other functions) is also a relatively frequent pattern. For example:
let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();
You may also be interested in Curry, so you can read it!
Function combination is to combine multiple simple functions to create more complex functions. So you can have a averageArray
function that combines the average function with the sum function that sums the values of the array. The individual functions are small and can be repeated for other purposes and combined together to perform more complete work.
// 纯函数 function squareNumber(number) { return number * number; } squareNumber(2);
Advantages
Functional programming generates modular code. You have a small number of functions that you can use repeatedly. Understanding the specific functions of each function means it should be simple to pinpoint errors and write tests, especially if the function output should be predictable.
Also, if you try to use multiple cores, you can distribute function calls to those cores, so it can improve computational efficiency.
How to use functional programming?
You don't need to turn completely to functional programming to integrate all of these ideas. You can even use many ideas well with object-oriented programming, which are often considered as their rival.
For example, React incorporates many functional principles, such as immutable states, but has mainly used class syntax for many years. It can also be implemented in almost any programming language - you don't need to write Clojure or Haskell unless you really want to.
Even if you are not a purist, functional programming principles can produce positive results in your code.
Frequently Asked Questions about Functional Programming
Functional programming is based on some key principles. First is invariance, which means that once a variable is set, it cannot be changed. This eliminates side effects and makes the code easier to understand. The second principle is pure functions, which means that the output of a function is determined only by its input, without any hidden input or output. The third principle is first-class functions, which means that functions can be used as input or output to other functions. This allows for higher-order functions and makes the code more concise and easier to understand.
The main difference between functional and procedural programming is the way they process data and state. In procedural programming, program states are stored in variables and can be changed over time. In functional programming, the state does not change, but creates a new state from the existing state. This makes functional programming easier to predict and debug because there are no side effects to worry about.
Functional programming provides many benefits. It can make the code easier to read and understand because it avoids side effects and mutable states. It also makes the code more reliable, as it encourages the use of pure functions that always produce the same output for the same input. In addition, functional programming can make the code easier to test and debug because functions can be tested in isolation.
While functional programming has many benefits, it also has some challenges. It can be difficult to learn, especially for people accustomed to procedural or object-oriented programming. Implementing certain algorithms in functional style can also be more difficult. Furthermore, functional programming sometimes leads to less efficient code, as it often involves creating new objects rather than modifying existing objects.
Many programming languages support functional programming to some extent. Some languages, such as Haskell and Erlang, are purely functional, while others, such as JavaScript and Python, are multi-paradigm languages that support functional programming and other paradigms. Even languages traditionally unrelated to functional programming, such as Java and C, have added functionality to support functional programming in recent years.
In functional programming, avoid side effects as much as possible. This is done by using pure functions that do not change any state or perform any I/O operations. When side effects are needed, they are isolated and controlled. For example, in Haskell, side effects are treated with monads, which encapsulate side effects and provide a way to link them together in a controlled way.
A higher-order function is a function that takes one or more functions as parameters, returns a function as its result, or performs both operations at the same time. Advanced-order functions are a key feature of functional programming because they allow functions to be used as data. This can lead to cleaner and more expressive code.
Recursion is a technique in which a function calls itself in its own definition. In functional programming, recursion is often used as a replacement for loops because loops involve mutable states, which is avoided in functional programming. Recursion can be used to solve a wide variety of problems, from calculating factorials to traversing trees.
Currying is a technique in functional programming in which a function with multiple parameters is converted into a series of functions, each function has only one parameter. This allows part of the function to be applied, where one function applies to some of its parameters and returns a new function that takes the rest of the parameters.
Functional Reactive Programming (FRP) is a programming paradigm that combines functional and reactive programming. In FRP, program states are modeled as a series of immutable values that vary over time, and functions are used to transform and combine these values. This makes it easier to reason about asynchronous and event-driven programs because it avoids mutable states and side effects.
The above is the detailed content of What Is Functional Programming?. For more information, please follow other related articles on the PHP Chinese website!