Pragmatic functional programming

Hello, Habr! I bring to your attention a translation of the article “Pragmatic Functional Programming” by Robert C. Martin (Uncle Bob).



The transition to functional programming seriously developed only about ten years ago. We see languages ​​such as Scala, Clojure, and F # attract attention. In fact, it was a big step in programming: “Oh, cool, a new language!” - enthusiasm ... Apparently there was something special - well, or that's what we thought.



Moore’s law tells us that computer speeds will double every 18 months. This law was in force from the 1960s to 2000. Then he stopped. The frequency reached 3 GHz, and then completely rose to a plateau. We have reached the speed of light! Signals cannot propagate over the surface of the chip fast enough to provide higher speeds.



This has led equipment engineers to change their strategy. In an attempt to increase throughput, they added more processors (cores). And to make room for these cores, they removed most of the caching and pipelining equipment from the chips. Because of this, processors have become much slower than before; however, there are more of them. As a result, the throughput increased.



I got my first dual-core computer 8 years ago. Two years later, I already had a quad-core computer. From this began the spread of nuclei. And after all, the most interesting thing is that we all understood that this would affect software development in ways that we could not imagine.



One of our answers was the study of functional programming (FP). FP strongly discourages changing the state of a variable after initialization. This has a profound effect on concurrency. If you cannot change the state of a variable, then you cannot have a race state. If you cannot update the value of a variable, then you should have no problem updating at the same time.



Of course, this was considered a solution to the multi-core problem. But with the proliferation of nuclei, parallelism, it became clear - NO, simultaneity will become a serious problem. FP should provide a programming style that would reduce the problems of working with 1024 cores in one processor.



On this basis, everyone set about studying Clojure, or Scala, or F #, or Haskell; because they knew that a freight train was coming to them, and they were ready by the time he arrived.



But the freight train never arrived. As I said earlier, six years ago I got a quad-core laptop. From that moment I had 2 more of the same laptop. The next laptop that I received looked outwardly as if it were also quad-core. Apparently we see another plateau (Moore's law)?

Also, last night I watched a 2007 film. In this film, the heroine used a laptop, browsed pages in a browser, used Google and received sms on her phone. It was too familiar to me. Oh ... it was old school - in this film I saw a pretty old laptop with an ancient version of the browser, and the flip phone was very different from modern smartphones. However, the change was not as significant as the change from 2000 to 2011. And not as dramatic as it would be from 1990 to 2000. Do we see the plateau in the speed of computer and software technology?

So, AF is not such an important skill as we once thought. Maybe we will not be littered with kernels. Perhaps we do not need to worry about chips with 32,768 cores on them. Maybe we can all relax and return to updating our variables again.



I think this will be a mistake. And a very big mistake. This will be as much a misunderstanding as the rampant use of Goto. I also have the thought that it will be as dangerous as refusing to send dynamically.



Why, you ask? Let's start the reasons that interested us in the first place. FP makes concurrency much safer. If you are building a system with a large number of threads or processes, then using the FP will greatly reduce the problems that you may encounter with race conditions and simultaneous updates.



Why else? For example, FP is easier to write, easier to read, easier to test and easier to understand. Now I imagine how some of you wave your hands and scream at the screen. You tried AF, and it was not easy to find you. All this mapping, reduction, and all recursion — especially tail recursion — is far from easy to understand. Of course I understand that. But for now, it's just a dating problem. As soon as you become familiar with these concepts - and by the way it does not take long to develop this acquaintance - programming will become much easier for you.



Why does it get easier? Because you do not need to monitor the status of the system. The state of variables may not change; thus, the state of the system remains unchanged. And this is not just a system that you do not need to track. You do not need to monitor the state of the list, or the state of the array, or the state of the stack, or queue; because these data structures cannot be changed. When you push an item onto the stack in the FP language, you get a new stack, you don't change the old one. This means that the programmer must juggle fewer balls in the air at the same time. After all, there is less to remember. Less needs to be tracked. And that is why code is much easier to write, read, understand and test.



So which FP language should you use? Personally, my favorite is Clojure. The reason is that Clojure is very simple. This is a dialect of Lisp, a beautiful language. Let me show you.



Here is the function in Java: f (x);



Now, to turn this into a function in Lisp, you simply move the first bracket to the left: (fx).



Now you know 95% Lisp and 90% Clojure. Amazing right? The syntax of silly little brackets is almost all the syntax that these languages ​​have. They are very simple.



You may have seen Lisp programs before, and you don’t like all these brackets. Most likely, you still do not like CAR, CDR, CADR, etc. Do not worry. Clojure has a little more punctuation than Lisp, so there are fewer parentheses. Clojure also replaced CAR and CDR and CADR with first, rest and second. Clojure is built on the JVM and gives you full access to the entire Java library and any other Java infrastructure or library that you use. Interoperability is quick and easy. And even better, Clojure provides full access to the OO features in the JVM.



“But wait!” I hear you say. “How is it that FI and OO are mutually incompatible!” Who told you that? This is complete nonsense. But the fact that in FP you cannot change the state of an object is true; so what? Just as inserting an integer onto the stack gives you a new stack, when you call a method that adjusts the value of an object, you get a new object instead of changing the old one. This is very easy to handle as soon as you get used to it.



But let's get back to OO. One of the features of OO that I find most useful at the software architecture level is dynamic polymorphism. And Clojure provides full access to Java dynamic polymorphism. This example will help me explain this best.



(defprotocol Gateway (get-internal-episodes [this]) (get-public-episodes [this]))
      
      





The code above defines a polymorphic interface for the JVM. In Java, this interface will look like this:



 public interface Gateway { List<Episode> getInternalEpisodes(); List<Episode> getPublicEpisodes(); }
      
      





At the JVM level, the bytecode generated is identical. Indeed, a program written in Java will implement the interface in the same way as if it were written in Java. In the same way, Clojure can implement the Java interface. In Clojure, it would look like this:



 (deftype Gateway-imp [db] Gateway (get-internal-episodes [this] (internal-episodes db)) (get-public-episodes [this] (public-episodes db)))
      
      





Note the db constructor argument and how all methods can access it. In this case, interface implementations simply delegate some local functions by passing db.



Perhaps best of all, Lisp and Clojure are Homoiconic, which means code is data that the program can manipulate. It is easy to see. The following code: (1 2 3) represents a list of three integers. If the first element of the list is a function, as in: (f 2 3), then it becomes a function call. Thus, all function calls in Clojure are lists; and lists can be directly controlled by code. Thus, a program can create and execute other programs.



The bottom line is this: functional programming is important and you should learn it. And if you are interested in which language is better to use to learn it, then I offer you Clojure.



All Articles