Perhaps every reader in this article has heard that functional programming came to this world with the expectation of bringing well-being to software development, you may also have heard that someone has regarded it as a silver bullet for software development. However, viewing more information on Wikipedia makes a lot of appetite for reference to the lambda algorithm and form system as soon as it comes up. It is difficult to see at a glance what this has to do with writing better software.
My Summary of effectiveness: most of the problems in software development are due to the fact that programmers do not fully understand all possible states in program execution. In a multi-threaded environment, this lack of understanding and the problems it causes become more serious. If you pay attention to these problems, you will find it almost serious to the point of panic. By writing a program in a functional style, you can clearly present the state to your code, so that the logic of the Code is easier to reason. In a pure functional system, this makes thread Competition Conditions impossible.
I do believe that functional programming has real value. However, I persuade all programmers to abandon the C ++ compiler, enable lisp and Haskell, or simply speak any other edge language, it is irresponsible. What makes Language designers always annoyed is that there will always be a lot of external factors that stress the benefits of cross-language development, especially in most fields. In addition to legacy code libraries and limited human resources, we also have cross-platform issues, private tool chains, certificate gateways, technology requiring authorization, and harsh performance requirements.
If you can use a non-mainstream language to complete the main development tasks in your work environment, you should cheer for it, but wait for a while. The charge is for project progress. For all others, writing a program in a functional style will benefit no matter what language you use. At any time, we should do so as long as it is convenient. When it is inconvenient, we should also think carefully about our decisions. In the future, if you want to, you can learn lambda, monad, and currying, synthesize functions that are lazy in the infinite set, and explicitly target all other aspects of functional language.
The C ++ language does not encourage functional programming, but it does not prevent you from doing so, it also provides you with the ability to go deep into the lower layer, use the internal functions of SIMD to directly layout data based on memory ing files, or any other essential features you find yourself using.
Pure Function
A pure function is a function that only displays the passed parameters. It returns one or more values calculated based on the parameters. It has no logical side effects. This is of course only an abstraction. At the CPU level, every function has side effects. Most functions have side effects on the heap level, but this abstraction still has value.
Pure functions do not display or update the global status, do not maintain the internal status, do not perform any I/O operations, and do not change any input parameters. It is best not to pass any irrelevant data to it-if an allmyglobals pointer is passed in, this goal will basically be shattered.
Pure functions have many good attributes.
- Thread SecurityPure functions using value parameters are completely thread-safe. If you use reference or pointer parameters, you should be aware of the risk that a thread that executes non-pure operations may change or release its data. However, in this case, pure functions are still a powerful tool for writing secure multi-threaded code. You can easily replace a pure function with a parallel implementation, or run multiple implementations and compare the results. This makes code testing and evolution easier.
- ReusabilityPorting a pure function to a new environment is much easier. The Type Definition and all other called pure functions still need to be processed, but there is no snowball effect. How many times do you know that the code of another system can meet your needs? But it is easier to rewrite it from all the assumptions about the system environment?
- TestabilityA pure function has a referential transparency, that is, it always returns the same result for the same set of parameters at any time, this makes it easier to use than those that are intertwined with other systems. I have never been very responsible for writing test code; too much code interacts with a large number of systems, so that using them requires very fine-grained control, however, I often persuade myself (maybe incorrect) that this payment is not worth it. Pure functions are easy to test. The test code is just as extracted from the Tutorial: construct some input and view the results. Every time I encounter a short piece of code that seems to be a little cool, I will split it into a separate pure function and write a test. What's terrible is that I often find problems in such code, which means that the test security net I have been testing is not big enough.
- Comprehensibility and maintainabilityRestrictions on input and output make it easier for pure functions to re-learn as needed, and fewer external information is hidden due to insufficient documents.
Automatic Reasoning of formal systems and software will become increasingly important in the future. Static code analysis is very important today. Converting code into a more functional style helps tools analyze it, or at least the problems covered by local tools that can make the speed faster are as much as the slow and expensive global tools. What we talk about in this industry is "Getting Things Done". I still cannot see the form of "correctness" about the entire program to prove that it can become a real goal, however, it is also very valuable to prove that a specific part of the Code does not have a specific type of problem. We can use more scientific and mathematical achievements in the development process.
Students who are taking an introduction to programming may be scratching their heads and thinking, "isn't all programs written like this ?" The reality is that there are many "Big Balls of mud" programs and few programs with clear architecture. The traditional imperative programming language provides you with a secure hatch, and as a result, they are always used. If you just write some code that is discarded after you use it, it is common to use the global status. If you write the code that will still be used one year later, you need to balance the current convenience factors with the inevitable troubles in the future. Most programmers are not good at predicting the pain points that will be caused by code changes in the future.
"Pure purity" practices
Not all things can be pure. Unless the program only operates its own code, it will always interact with the external world at a certain point. Trying to maximize code purity can bring unimaginable pleasure. However, to reach a pragmatic critical point, we need to acknowledge that side effects are necessary at a certain moment, and manage them effectively.
Even for a specific function, this is not a "all or none" goal. As the purity of a function increases, its value can continuously increase, in addition, the value from "almost pure" to "completely pure" is lower than the value from "spaghetti state" to "basically pure. As long as you move a function toward a pure goal, your code can be improved even if it cannot reach the full purity. It is not pure to increase or decrease the Global Counter or check a global debug flag, but if it is its only deficiency, it can still reap most of the benefits of the function.
Avoiding the worst results in a larger context is usually more important than achieving perfection in a limited situation. Consider the most unpleasant function or system you have ever dealt with. The one that can only be handled by armed forces is almost certain, there must be various assumptions on which complex state networks and code behaviors depend, and these complexities not only occur on parameters. In these aspects, the constraints are enhanced, or at least the effort is made to prevent more code from falling into similar chaos, resulting in a much greater impact than squeezing several underlying mathematical functions.
Refactoring Code toward a pure objective usually involves freeing computing from its running environment, which almost inevitably means more parameters are passed. It seems a little strange-the cumbersome and cumbersome in programming languages have been scolded, while functional programming is often related to the reduction of code size. Programs Written in functional programming languages are more concise than imperative languages. The factors are orthogonal to the use of pure functions, these factors include garbage collection, powerful built-in types, pattern matching, list derivation, function synthesis, and various syntactic sugar. Most of the decrease in the program volume is not related to the functional language, and some imperative languages can also bring the same effect.
If you have to pass more than 10 parameters to a function, you should be annoyed. You can refactor the code by reducing the complexity of parameters. C ++ does not have any language support for maintaining pure functional language, which is indeed not ideal. If someone makes a large number of basic functions useless in some bad ways, all the code that uses this function will lose its purity. From the perspective of the form system, it sounds disastrous, but it is still the saying that this is not the idea of having nothing to do with Buddhas. Unfortunately, the problems in large-scale software development can only be statistically significant.
It seems that it is necessary to add a "pure" keyword to the future C/C ++ language standards. C ++ already has an approximate keyword const-an optional modifier that supports checking programmer intent during compilation, and it does not harm the code. The D language provides a "pure" Keyword: http://www.d-programming-language.org/function.html. Note that their distinction between weak purity and strong purity-to be pure, references or pointers in input parameters must be modified using Const.
In some ways, the language keyword is too strict-a function can still be pure even if a non-pure function is called, as long as the side effects do not escape from the function. If a program only processes command line parameters without operating random file system states, the entire program can be seen as pure functional units.
Object-Oriented Programming
Michael feathers (Twitter @ mfeathers) said: OO encapsulates mobile parts to make the code understandable. FP makes the code understandable by minimizing the moving parts.
"Moving parts" is the status in change. Notifying an object to change itself is the first lesson in the basic object-oriented programming textbook. It is rooted in the idea of most programmers, but it is an anti-functional behavior. This basic OOP idea has obvious value for organizing functions together with the data structures they operate on, but if you want to get the benefits of functional programming in some of your code, in this case, you must isolate some object-oriented behaviors.
Classes and methods that cannot be declared as const are not purely defined, because they need to modify some or all of the State sets of objects, this set may be very large. They are not thread-safe either. Here, we just click it and set the object to an unexpected state at. This power is the inexhaustible source of the bug. Without considering the implicit const this pointer, the const object method can still be regarded as a pure function from the technical point of view. However, many objects are very large, so large that they are enough to form a global state, this weakens some of the benefits of pure functions in terms of simplicity and clarity. Constructors can also be pure functions. They should usually try to make them pure functions-they accept parameters and return an object.
From the perspective of flexible programming, you can often use more functional methods to use objects, but some interface changes may be required. In id software, we have been using an idvec3 class for ten years. It has only one void normalize () method, but there is no corresponding idvec3 normalized () const method. Many string methods are defined in a similar way. They operate on themselves, rather than returning a new copy of the corresponding operation, such as tolowercase () and stripfileextension.
Performance impact
Under any circumstances, direct modification of memory blocks is almost an insurmountable optimal solution. Without such modification, performance will inevitably be sacrificed. Most of the time, this is only a theoretical benefit. We have always been using performance for productivity.
Using pure function programming will lead to more data replication. For performance considerations, this will obviously become an incorrect implementation strategy in some cases. For an extreme example, you can write a pure function drawtriangle (), accept a frame cache (framebuffer) parameter, and return a brand new frame cache with a triangle drawn as the result. Don't do this.
Returning all results by value is a natural functional programming style. However, it is harmful to the performance to always rely on the compiler to implement the return value optimization. Therefore, for the complex data structure of function output, it is often reasonable to pass a reference parameter, but there is also a bad side: It prevents you from declaring the return value as const to avoid multiple assignments.
In many cases, people have a strong desire to update a value in the passed complex structure, instead of copying a copy and returning the modified version, which means that thread security is not guaranteed, therefore, do not do this easily. The generation of a list is a reasonable situation where local updates can be considered. Append a new element to the list. The pure function method is to return a new list copy containing the new element at the end of the list. The original list remains unchanged. The real functional language uses a special method in implementation, so that the consequences of such behavior are not as bad as they are, but if you do this on a typical C ++ container, then you will die.
An important mitigation factor is that today's performance means parallel program design. Compared to a single-threaded environment, parallel programs require more replication and merge operations even in the best performance, therefore, the loss caused by replication is reduced, and the benefits of Complexity Reduction and correctness improvement are correspondingly increased. For example, when you begin to consider running all the roles in a game world in parallel, you will gradually understand that it is difficult to update objects using object-oriented methods in a parallel environment. Maybe all objects reference a read-only version of the world state, but the updated version is copied at the end of a frame ...... Hi, wait ......
Action
Check some complex functions in your own code library, track the external status of each bit it can touch and all possible status updates. Even if you do not make any changes to it, it is an excellent document to put the information into a comment block. If a function can -- for example, trigger a screen refresh through a rendering system, you can directly hold your hands in the air and declare that all the positive side effects of this function are beyond human understanding. The next task you want to start is to rethink this function from the beginning based on the actual executed computing. Collect all input, pass it to a pure function, and then receive the result and process it accordingly.
When debugging the code, let yourself focus on the updated status and hidden parameters, so as to conceal the actual action part. Modify the code of some tool objects so that the function returns a new copy instead of modifying itself. In addition to the iterator, try to add const before each variable used by the function.