Functional programming has always been a hot topic in the technology community, especially in the front-end community known as entertainment. Object-oriented and imperative programming are often cited as counterexamples in numerous introductory articles, as if they were ugly and dirty old technologies. The fear of such overcorrection is the original intention of this article.

Why is political correctness involved here? That’s because the obsession with programming paradigms has become more or less a form of moral kidnapping: it’s close to the powerful moral beliefs behind political correctness. This belief is mixed with a belief in programming languages, and the result is unforced control of speech. Take the following idea:

  • Your code uses a for loop, which is procedural. For elegance, you should write it as a function.
  • Your code has side effects. It’s dirty. For purity, you should wrap IO in Monad.
  • You’re using class in this code, which is object-oriented. To be stateless, you should write higher order functions.

How is this crude logic different from “Parents are for the best” and “women are not good programmers”? A lot of this irresponsible rhetoric contributes to the current Stereotype of object-oriented and imperative programming in the community. In fact, the idea that functional programming is opposed to object-oriented/imperative programming is itself loosely classified. Despite this, these arguments are rather arbitrary and ignore where the technology is applicable.

For example, one of the main accolades for functional programming is the stateless nature of pure functions. We’ve heard a lot about its benefits: predictable results, good for testing, good for reuse, good for concurrency… It all sounds so ideal, so can we write a complete system in pure functions from the bottom up? The paradox here is that the closer you get to what a computer looks like under the hood, the further away you get from pure functions and statelessness.

When you try to understand the real basics of CPU/graphics/networking, they are not functional at all:

  • The CPU itself is the most typical state machine. In my weekend toy Chip-8 simulator, for example, the CPU state is a bunch of registers, stack Pointers, and counters modeled with a few variables. Each instruction is a function that modifs the global state — not pure, of course, but very intuitive and abstract about how the CPU works.
  • The apis that drive the graphics cards are hideously stateful. Anyone who has tried to build a WebGL rendering pipeline from scratch will know what this means. And in the next generation of display-driven apis that squeeze the most out of performance into their orientation, the state of human maintenance will be even more trivial.
  • The migration of state machines in the network protocol stack has been sketched out countless times in the textbook Computer Networks. Even in the dumbest Web page that has been encapsulated in the application layer, look at the console to execute a sentenceperformance.timingIt is not easy to draw the state transition relationship between these fields correctly.

When you want to understand how these things work, the most classic data you can look up and the industry’s most battle-tested implementations are almost always very stateful in a procedural, imperative programming paradigm. Are the developers who write these fundamental engineering feats less skilled than the evangelists of functional programming?

The paradox boils down to this: the idea of functional programming is closer to theoretical mathematics. The imperative and procedural programming is closer to the way hardware works in actual engineering. The simplicity of the two is reflected in different dimensions. As an example, the C-style while loop, which many functional programming enthusiasts despise or even despise, is a very easy design to implement and hardware optimize. If you’ve ever tried to read the assembly code generated by GCC, it’s easy to see how a while can be associated with a jump instruction in assembly:

JMP LOOP ; Start the loop by jumping to the bottom. BEGIN: NOP; Null instruction placeholder; . This is where we start putting the code in the body of the loop; . ; . ; . LOOP: CMP... ; Check conditions JNE BEGIN; If no, go to the BEGIN positionCopy the code

The for loop, reviled by functionalists, is implemented by adding initialization and counter temporary variables to the control flow above. Detested syntaxes such as break and continue can be implemented flexibly on JMP by adding more labels. Imperative loop syntax is obviously easier to implement than a series of jump logic that requires careful saving and restoring context when a function is called and returned — of course, you could argue that functional languages like Scheme have interpreters that are easier to implement, I did implement an interpreter for a Scheme dialect language as a weekend toy. But how practical is this language, which is almost a handwritten syntax tree, for engineering purposes? A similar analogy exists in the abstraction of various mathematical calculations. Such as the gl-matrix matrix operation library contributed by PR, a large number of mutation is also very non-functional, but it is still very simple, reliable and efficient in fact.

For fans of functional programming, the all-in favor of recursive functions is a bit confusing: your for loop is too low, and I’m writing elegant tail-return with interpreter optimizations that won’t burst the stack. Admittedly, using recursion when dealing with nested data structures is fairly clean and readable. This is what I learned when I tried to implement recursive descent and LALR parsers for my homework before graduation: non-recursive writing can be very verbose. However, this approach to readability, which comes at a performance cost, is often misinterpreted as a good implementation and abused. For example, a deep copy algorithm is simple to implement recursively, but it objectively risks Stack Overflow. As a solution, we can implement it using array emulation stacks. Your code is less functional: do you refuse to fix stack overflow bugs because functional code is more elegant?

A similar disregard for proximity is also reflected in the promotion of concepts that are actually more obscure. Monad, a monoid ona functor, is a concept that will scare many beginners who want to get started with functional programming. Of course, you can use the Hooks and promises that are so simple to use that they are called Monad, and I did write an article on why Promise is a Monad in terms of identity and associativity when I got started. However, using a more difficult set of concepts to formally define and explain something that is very useful and easy to understand in actual development is probably not conducive to the popularization of good tools and ideas. For example, the Japanese concept of function is called kankei. Not knowing this word in the Japanese system does not affect the consistency of a Chinese user’s knowledge system and the use of functions. Moreover, Monad is actually equivalent to a [design pattern] in the functional programming paradigm, and the abandonment of design pattern, is not one of the advantages of functional programming itself?

When it comes to design patterns, software engineering is unavoidable. At this point, we have a lot of tao level design principles, but the principles that really Scale Up engineering are not related to specific programming paradigms. For example, we all know that high-cohesion, low-coupling module partitioning is maintainable, but what matters most in this dimension is not the functional-related syntactic features, but the language’s package manager and module loading specification. As well as changing the ECS architecture in the game industry, its evolution in the direction of composition over inheritance is still possible in object-oriented languages. Even in real engineering cases, the largest and most reliable implementations of the deep UI realm of the front end are still very object-oriented — Windows and macOS desktop UI environments are not derived from functional languages, Will the operating system desktop manager manage global state in a single store like Redux does?

At this point, the ridicule of this article should come to an end. But we certainly shouldn’t write to vent, and I’m not a big fan of imperative programming (on the contrary, I wrote an Amway article about RxJS simulating elevator scheduling). It’s time to make some clarifications and appeals:

  • Functional programming is important and worth learning and popularizing in many niches. However, it is not universally superior to other paradigms such as object-oriented or procedural.
  • Functional programming also has its own inherent flaws, which should be looked at objectively.
  • We want to look at all paradigms equally, keep an open mind, reject sensationalism, and choose a more efficient development and operational solution based on actual needs.

In actual coding, I focus more on the “intuitive” aspect. There are roughly two dimensions:

  • Whether a language feature is used in a way that matches the scenario it is designed to solve.
  • Whether the implementation of a specific requirement conforms to the simplest and most straightforward abstraction.

It doesn’t matter whether a cat is black or white as long as it catches mice. As long as it’s high quality code that is readable and maintainable, why care which paradigm it belongs to 🙂