Functional Programming is Terrible

Functional programming is somewhat popular at CMU. There is even a course about it. However, functional programming is the worst programming paradigm. For the marketing points of the course,

  • FP isn’t better for program verification: Verification is almost trivial without mutable states, in almost all programming languages. Analyzing control flow, etc. isn’t hard as long as the syntax is well-defined. States are the challenge to automatic verification, and functional programming is supposed to be immutable and have no state. But you can’t avoid states in real life. States are what makes software useful. If you write a program without mutable states, it is essentially one huge expression and just some adapter code. You can’t even implement dynamic programming without some kind of states for memorization.
  • FP makes parallelism harder: It is true that making more things immutable can reduce the possibility for race conditions. But all common languages have const or final that control it better. And other languages have better parallelism ecosystem. C++ has CUDA/SIMD instrinstics, Go has goroutines, Java has Hadoop, and there are many others.
  • Abstraction: It’s way easier to implement proper abstraction with OOP languages. OOP is mostly just about abstraction/encapsulation, and it does really well at that. OCaml has better abstraction by learning from OOP, but the syntax is still somewhat counterintuitive.

And most FP languages use terrible language constructs,

  • ADT: Abstract data types is a very inefficient way to represent data. ADT is often used recursively (if not, it is as harmless as std::variant). And it can be huge: there are often programs that store trees in ADT. The data have to be allocated on heap (compilers can’t know the size in advance) and all nodes are tagged (to store the variant). Now some seemingly innocuous data structures can be very inefficient.
  • Pattern matching: Pattern matching has to be sequential because of the pattern matching priority from top to bottom, which makes it no more useful than a long list of if/else. And unfortunately you can’t avoid pattern matching because of ADT.
  • FP significantly decreases code readability and maintainability: Lack of common features like loops, you have to use lambda functions instead. This is much more difficult to read than something like range-based for in most languages. You have to write a function to print 5 “Hello, world!” and good luck on your stack usage. And it becomes worse with high order function, it just wastes programmer effort to understand the code and the control flow. Most languages actually have a human-readable control flow, as early as Fortran! Don’t overuse lambda functions, in any language.
  • Code reuse: There is essentially no polymorphism in FP languages. The type system is at most something like C++20 concepts and broken generics, that’s NOT polymorphism.
  • FP makes complexity analysis harder: Do you want to analyze the time complexity of a huge mess of lambdas, function arguments, closures and expressions?