JavaScript function combination: build more maintainable code
JavaScript Function Composition is a technique of combining multiple simple functions into a single more complex function that performs subfunction operations on a given data in logical order. The order in which functions are applied produces different results.
To write maintainable code, it is crucial to understand the return type of each combined function to ensure that the next function can handle it correctly. JavaScript does not prevent the combination from returning functions of inappropriate types, so this part of the responsibility falls on the programmer.
For those who are not familiar with the functional programming paradigm, combining functions can make the code seem complicated. However, with the advent of ES2015 syntax, using arrow functions, it is possible to create simple combination functions in one line of code without the need for special combination methods.
Combined functions should be pure functions, which means that each time a specific value is passed to a function, the function should return the same result, and the function should not have side effects of changing its own external values. This will produce cleaner, more readable code.
This article was reviewed by Jeff Mott, Dan Prince and Sebastian Seitz. Thanks to all SitePoint peer reviewers for getting SitePoint content to its best!
One of the advantages of functional thinking is the ability to build complex functions using small, easy-to-understand single functions. But sometimes this requires thinking about the problem in reverse, not positively, in order to figure out how to create the most elegant solution. In this article, I will take a step-by-step approach to checking the combination of functions in JavaScript and demonstrate how it produces code that is easier to understand and has fewer errors.
Nested functions
Composition is a technique that allows you to combine two or more simple functions into a more complex function that executes each subfunction on any data passed in logical order. To get this result, you need to nest one function in another and repeat the operations of the external function on the result of the inner function until the result is produced. And the results may vary depending on the order in which the function is applied. This can be easily demonstrated using programming techniques we already know in JavaScript by passing a function call as a parameter to another function:
function addOne(x) { return x + 1; } function timesTwo(x) { return x * 2; } console.log(addOne(timesTwo(3))); //7 console.log(timesTwo(addOne(3))); //8
In this case, we define an addOne() function to add 1 to a value, and a timesTwo() function to multiply a value by 2. By passing the results of one function as an argument to another, we can see how nesting one of them in the other produces different results, even if the initial values are the same. The inner function is executed first, and the result is passed to the outer function.
Imperative combination
If you want to repeat the same sequence of operations, it may be convenient to define a new function to automatically apply the first and second function. This might look like this:
function addOne(x) { return x + 1; } function timesTwo(x) { return x * 2; } console.log(addOne(timesTwo(3))); //7 console.log(timesTwo(addOne(3))); //8
In this case, we manually combine the two functions together in a specific order. We create a new function that first assigns the passed value to the holder variable, then updates the value of that variable by executing the first function, followed by the second function, and finally returns the value of that holder. (Note that we use a variable called holder to temporarily hold the value we passed in. For such a simple function, the extra local variables seem redundant, but even in imperative JavaScript, the arguments to the function will be passed in It is also a good habit to treat values as constants. It is possible to modify them locally, but this creates confusion about what the parameter values are when called at different stages of a function.) Similarly, if we want to create another new function to the contrary The order of applying these two smaller functions, we can do the following:
// ...来自上面的先前函数定义 function addOneTimesTwo(x) { var holder = x; holder = addOne(holder); holder = timesTwo(holder); return holder; } console.log(addOneTimesTwo(3)); //8 console.log(addOneTimesTwo(4)); //10
Of course, this code starts to look very repetitive. Our two new combined functions are almost identical, except that the two smaller functions they call have different execution orders. We need to simplify it (like not repeating ourselves). Also, using temporary variables that change their value like this is not very functional, even if it is hidden inside the combinatorial function we are creating. Bottom line: We can do better.
Create functional combinations
Let's create a combinatorial function that takes existing functions and combines them together in the order we want. In order to do this in a consistent way without having to deal with internal details every time, we have to decide the order in which we want to pass the functions as parameters. We have two options. The parameters will be functions, respectively, which can be executed from left to right or from right to left. That is, using the new function we proposed, compose(timesTwo, addOne) can represent timesTwo(addOne()) to read parameters from right to left, or addOne(timesTwo()) to read parameters from left to right. The advantage of executing parameters from left to right is that they will be read in the same way in English, which is very similar to how we named the combined function timesTwoAddOne() to imply that multiplication should happen before addition. We all know the importance of logical naming for concise and easy-to-read code. The disadvantage of executing parameters from left to right is that the value to be operated must be placed in front. However, putting the value in front makes it less convenient to combine the resultant functions with other functions in the future. To explain the thought behind this logic well, you can’t go beyond Brian Lonsdorf’s classic video Hey Underscore, You’re Doing it Wrong. (Although it should be noted that now Underscore has an FP option that can help solve the functional programming problem that Brian discusses when using Underscore with functional programming libraries such as lodash-fp or Ramda.) Anyway, we really want to What you want to do is first pass in all configuration data, and then finally pass in the value you want to operate on. Therefore, it is most logical to define our combined functions as reading their parameters and applying them from right to left. So we can create a basic combination function that looks like this:
function addOne(x) { return x + 1; } function timesTwo(x) { return x * 2; } console.log(addOne(timesTwo(3))); //7 console.log(timesTwo(addOne(3))); //8
Using this very simple combination function, we can build our two previous complex functions more simply and see the same result:
// ...来自上面的先前函数定义 function addOneTimesTwo(x) { var holder = x; holder = addOne(holder); holder = timesTwo(holder); return holder; } console.log(addOneTimesTwo(3)); //8 console.log(addOneTimesTwo(4)); //10
While this simple combinatorial function works, it does not consider many issues that limit its flexibility and applicability. For example, we might want to combine more than two functions. Furthermore, we lose this information in the process. We can solve these problems, but it is not necessary to master how the combination works. Rather than writing it yourself, inheriting a more powerful combination from existing functional libraries such as Ramda, by default it does take into account the right-to-left order of parameters.
Type is your responsibility
It is important to remember that it is the responsibility of the programmer to know the return type of each combined function so that the next function can handle it correctly. Unlike pure functional programming languages that perform strict type checking, JavaScript does not prevent you from trying to combine functions that return values of inappropriate types. You are not limited to passing numbers, but not even to keeping the same variable type from one function to the next. But you are responsible for making sure that the function you are combining is ready to process any value returned by the previous function.
Consider your audience
Always remember that others may need to use or modify your code in the future. Using combinations in traditional JavaScript code can be complicated for those who are not familiar with the functional programming paradigm. The goal is to be simpler, easier to read and maintain code. However, with the advent of ES2015 syntax, it is even possible to create simple combination functions as single-line calls using arrow functions without the need for special combination methods:
function addOne(x) { return x + 1; } function timesTwo(x) { return x * 2; } console.log(addOne(timesTwo(3))); //7 console.log(timesTwo(addOne(3))); //8
Let's start combining today
As with all functional programming techniques, it is important to remember that your combined functions should be pure functions. In short, this means that every time a specific value is passed to a function, the function should return the same result, and the function should not have the side effects of changing its own external values. Combination nesting is very convenient when you have a set of related functions you want to apply to your data, and you can break the components of that function into reusable and easy to combine functions. As with all functional programming techniques, I recommend adding the combination to your existing code with caution to familiarize yourself with it. If you do it right, the result will be cleaner, drier, and easier to read code. Isn't this what we all want?
(The following is FAQ, which has been adjusted and streamlined according to the original text, and duplicate information is avoided)
Frequently Asked Questions about Function Combination and Maintainable Code
What is a combination of functions in programming? Function combination is the concept of combining simple functions into more complex functions. You can create complex and powerful functions with these small reusable functions just like building blocks. This helps make the code more readable, easier to maintain, and more scalable.
How does function combination promote maintainable code? Function combinations promote maintainability by encouraging the use of small, reusable functions. These functions are easier to understand, test, and debug. When these small functions are combined to create more complex functions, it is easier to pinpoint and fix any issues that may arise. This modular approach also makes it easier to update or modify certain parts of the code without affecting the entire system.
What are the best practices for writing maintainable code? Writing maintainable code involves a number of best practices, including: keeping functions small and focusing on a single task; using meaningful variables and function names; annotating code to explain the “why” behind the code logic; following Consistent coding style and structure.
What is the relationship between function combination and functional programming? Function combination is a basic concept in functional programming. Functional programming is an example of treating calculations as evaluation of mathematical functions and avoiding changing states and variable data. In this paradigm, function combinations play a crucial role in building more complex functions from simpler functions, thereby improving the reusability and maintainability of the code.
What are the challenges of implementing function combinations? Function combinations have many benefits, but they can also bring challenges. One challenge is that if not managed properly, it can lead to hard to read code, especially when combining multiple functions. Handling errors and exceptions in a chain of combined functions can also be difficult. However, these challenges can be mitigated by good coding practices and correct use of function combinations.
How to improve code testing with function combinations? Function combinations can make code testing easier and more efficient. Since a combined function is composed of smaller, independent functions, you can test each small function separately. This makes it easier to isolate and fix bugs. It also facilitates the practice of unit testing, where each part of the software is tested individually to ensure it works properly.
The above is the detailed content of Function Composition: Building Blocks for Maintainable Code. For more information, please follow other related articles on the PHP Chinese website!