Programming models change extremely slowly. So far, we’ve seen new models emerge every twenty years or so. My training started with some procedural languages, Basic and Fortran. In college, I learned a more structured approach with Pascal. At IBM, I started to code C and C++ commercially and was introduced to Java for the first time. I also began writing object-oriented code. My programming experience has spanned more than thirty years, and I’ve seen only two major programming paradigms. You might be asking yourself why I am so enthusiastic about introducing a few other programming paradigms. It’s a fair question.
Though programming paradigms change slowly, they do change. Like a tornado’s path, they can leave behind some devastation, taking the form of broken careers and companies that invested poorly. When you find yourself fighting a programming paradigm, you need to start paying attention. Concurrency and reliability are starting to nudge us in the direction of a higher-level programming language. Minimally, I think we’re going to start to see more specialized languages to solve specific problems. These are the programming models we encountered.
The current “king of the hill” is object orientation, typically in the Java language. This programming paradigm has three major ideas: encapsulation, inheritance, and polymorphism. With Ruby, we experienced dynamic duck typing. Rather than enforcing a contract based on the definition of a class or objects, Ruby enforced typing based on the methods an object could support. We learned that Ruby supported several functional concepts through code blocks.
Scala, too, offered object-oriented programming. Though it supports static typing, it is much less verbose than Java, offering features such as type inference to simplify the syntax. With this feature, Scala automatically deduces the type of variables based on clues in syntax and usage. Scala goes beyond Ruby to introduce functional concepts.
Both of these languages run in widespread production applications today, and both represent significant advances in language design compared to mainstream languages such as Java. There are many variations of object-oriented languages, including the next programming paradigm, prototype languages.
You could say that prototype languages are a subset of object-oriented languages, but they are just different enough in practice that I introduced them as a different programming model. Rather than working through a class, all prototypes are object instances. Some specially designated instances serve as prototypes for other object instances. This family of languages includes JavaScript and Io. Simple and expressive, prototype languages are typically dynamically typed and work well for scripting and application development, especially for user interfaces.
As you learned within Io, a simple programming model with a small, consistent syntax can be a powerful combination. We used the Io language in broadly different contexts ranging from scripting concurrent programs together to coding our own DSL. But this prototype programming was not the most specialized paradigm that we encountered.
Prolog comes from a family of programming languages built for constraint-logic programming. The different applications we built with Prolog solved a fairly narrow type of problem, but the results were often spectacular. We defined logical constraints that we knew about our universe and had Prolog find the solution.
When the programming model fit this paradigm, we were able to get results with a fraction of the lines of code that it would take in other languages. This family of language supports many of the most critical applications in the world in domains such as air traffic control and civil engineering. You can also find crude logical rules engines in other languages such as C and Java. Prolog served as the inspiration of Erlang, from another family of languages in this book.
Perhaps the most widely anticipated programming paradigm in this book is functional programming. The degree of purity found in functional programming languages differs, but the concepts are consistent throughout. Functional programs are made up of mathematical functions. Calling the same function more than once will give you the same result each time, and side effects are either frowned on or forbidden. You can compose those functions in a variety of ways.
You’ve seen that functional programming languages are usually more expressive than object-oriented languages. Your examples were often shorter and simpler than the object-oriented counterparts because you had a broader range of tools for composing programs than you did in the object-oriented paradigm. We introduced higher-order functions and also complex concepts such as currying as two examples that you can’t always find in object-oriented languages. As you learned in Haskell, different levels of purity lead to different sets of advantages and disadvantages. One clear win for the functional languages was the absence of side effects, making concurrent programming easier. When mutable state goes away, so do many of the traditional concurrency problems.
If you do decide that you want to do more functional programming, there are several different ways to get there. You can make a clean break from OOP, or you can pick an approach that is slightly more evolutionary.
As we waded through each of the seven languages, you saw languages spanning four decades and at least as many programming paradigms. I hope you can appreciate the evolution of programming languages as well. You saw three distinctly different approaches to evolving paradigms. With Scala, the approach is coexistence. The Scala programmer can construct an object-oriented program using strong functional tendencies. The very nature of the language is that both programming paradigms are first-class. Clojure takes the approach of compatibility. The language is built on the JVM, allowing Clojure applications to use Java objects directly, but the Clojure philosophy is that certain elements of OOP are fundamentally flawed. Unlike Scala, Clojure-Java Interop is provided to leverage frameworks existing on the Java virtual machine, not to broaden the programming language. Haskell and Erlang are basically stand-alone languages. Philosophically, they do not embrace object-oriented programming in any form. So, you can embrace both paradigms, make a clean break, or embrace object-oriented libraries but decide to leave the OOP paradigm behind. Take your pick.
Whether or not you choose to adopt one of the languages in this book, you’ll be better for knowing what’s out there. As a Java developer, I had to wait a decade for closures, mainly because people like me were uneducated and did not scream loud enough for them. In the meantime, mainstream frameworks like Spring were stuck with anonymous inner classes to solve problems that could have used closures extensively. My fingers bled from the oppressive amount of typing, and my eyes bled because I had to read that stuff. The modern Java developer knows much more, partially because people such as Martin Odersky and Rich Hickey gave us alternatives that are now pushing the state of the art and forcing Java to advance or get left behind.