Back in 2005, when Ruby on Rails started appearing on developers’ radars, there was an explosion of blogs and articles discussing how dangerous these loosey goosey languages were, with their hippy dynamic typing. And many predicted dire fates for companies foolish enough to take the plunge. Regular readers are certainly familiar with Ted Neward, who makes technology predictions each year on his blog. Here’s what Ted said on January 1, 2006:
Scripting languages will hit their peak interest period in 2006; Ruby conversions will be at its apogee, and it’s likely that somewhere in the latter half of 2006 we’ll hear about the first major Ruby project failure, most likely from a large consulting firm that tries to duplicate the success of Ruby’s evangelists (Dave Thomas, David Geary, and the other Rubyists I know of from the NFJS tour) by throwing Ruby at a project without really understanding it. In other words, same story, different technology, same result. By 2007 the Ruby backlash will have begun.
In Ted’s defense, he’s sometimes correct in his predictions. In his defense, there were people who misunderstood how to apply the technology, and there were some failures, although no where nearly as many as pundits predicted.
Current day, dynamic languages thrive. Major websites like GroupOn and LivingSocial use Ruby on Rails to build and maintain industrial strength sites, and even the website that spawned Rails, Basecamp, is still alive and well. The Rails framework itself was harvested out of the Basecamp Web application, illustrating that there is a Rails site that has survived all the messy parts of enterprise development: scalability, maintenance, user feature requests, etc., for almost a decade.
In fact, the most popular language in the world, JavaScript, has some of the most unforgiving aspects of any type system. I know that its popularity is accidental and not based on technical merit; my point is that even the most hostile of languages can be tamed when used properly.
For many language communities, rigorous testing added the safety net that static typing formerly provided, with of course, a slew of other engineering benefits. Testing is well established in Ruby on Rails, and mature development teams that use JavaScript also heavily test it.
Testing is critical for many popular dynamic languages because of the design of the language. An imperative, object-oriented language is designed to mutate state and move it around. If you think about the design features of object-oriented languages, many of them facilitate visibility and access of shared internal state: encapsulation, scoping, visibility, etc. When you mutate state in any language, testing is one of your best options to make sure that it’s happening correctly.
But given the apparent lack of danger of dynamic languages when used properly, perhaps it’s time we rethought the language characteristics that are important for the components of our applications. To do that, I need to discuss types.
Types of Types
Computer language types generally exist along two axises, pitting strong versus weak and dynamic versus static, as shown in Figure 1.
Static typing indicates that you must specify types for variables and functions beforehand, whereas dynamic typing allows you to defer it. Strongly typed variables “know” their type, allowing reflection and instance checks, and they retain that knowledge. Weakly typed languages have less sense of what they point to. For example, C is a statically, weakly typed language: variables in C are really just a collection of bits, which can be interpreted in a variety of ways, to the joy and horror (sometime simultaneously) of C developers everywhere.
Java is strongly, statically typed: you must specify variable types, sometimes several times over, when declaring variables. Scala, C# and F# are also strongly, statically typed, but manage with much less verbosity by using type inference. Many times, the language can discern the appropriate type, allowing for less redundancy.
Many times, the language can discern the appropriate type, allowing for less redundancy.
This diagram is not new; this distinction has existed for a long time. However, a new aspect as entered into the equation: functional programming.
Functional Functionality
Functional programming languages have a different design philosophy than imperative ones. Imperative languages try to make mutating state easier, and have lots of features for that purpose. Functional languages try to minimize mutable state, and build more general purpose machinery.
When you find reusable code in an object-oriented system, you harvest it by capturing a class graph. It’s no coincidence that every pattern in the Gang of Four book, Design Patterns: Elements of Reusable Object-Oriented Software, features one or more class diagrams. Functional reuse is a bit different. In functional programming languages, language designers have built general algorithmic machinery, based in part on the fascinating mathematics field of category theory, expecting data and customization via code or closure blocks.
A common philosophy in the functional programming world, particularly in Lisp communities like Clojure, is to have only a few data structures (lists and maps) with many algorithms (filter, map, reduce, folds, etc.) that operate on them. Doing so allows the designers to create hyper efficient operations because they focus on just a few things.
Another common philosophy in the functional world is to embrace immutability. When done at a low level, immutable data structures simplify many complex things: threading, serialization, etc.
But functional doesn’t dictate a typing system, as you can see in Figure 2.
With their added reliance, even insistence, on immutability, the key differentiator between languages now isn’t dynamic versus static, but imperative versus functional, with interesting implications for the way we build software.
Polyglot Pyramids
In my blog back in 2006, I accidentally re-popularized the term [Polyglot Programming] (http://memeagora.blogspot.com/2006/12/polyglot-programming.html) and gave it a new meaning: taking advantage of modern runtimes to create applications that mix and match languages but not platforms. This was based on the realization that the Java and .NET platforms support over 200 languages between them, with the added suspicion that there is no one true language that can solve every problem. With modern managed runtimes, you can freely mix and match languages at the byte code level, utilizing the best one for a particular job.
After I published my article, my colleague Ola Bini published a follow on paper discussing his Polyglot Pyramid, which suggests the way people might architect applications in the polyglot world, as shown in Figure 3.
In Ola’s original pyramid, he suggests using more static languages at the bottommost layers, where reliability is the highest priority. Next, he suggests using more dynamic languages for the application layers, utilizing friendlier and simpler syntax for building things like user interfaces. Finally, atop the heap, are Domain Specific Languages, built by developers to succinctly encapsulate important domain knowledge and workflow. Typically, DSLs are implemented in dynamic languages to leverage some of their capabilities in this regard.
This pyramid was a tremendous insight added to my original post, but upon reflection about current events, I’ve modified it. I now believe that typing is a red herring, distracting from the important characteristic, which is functional versus imperative. My new Polyglot Pyramid appears in Figure 4.
I believe that the resiliency we crave comes not from static typing but from embracing functional concepts at the bottom. If all of your core APIs for heavy lifting, like data access, integration, etc., could assume immutability, all that code would be much simpler. Of course, it changes the way we build databases and other infrastructure, but the end result will be guaranteed stability at the core.
Atop the functional core, use imperative languages to handle workflow, business rules, user interfaces, and other parts of the system where developer productivity is a priority. As in the original, DSLs sit on top, serving the same purpose. However, I also believe that DSLs will penetrate through all the layers of our systems, all the way to the bottom. This is exemplified by the ease in which you can write DSLs in languages like Scala (functional, statically strongly types) and Clojure (functional, dynamically strongly typed) to capture important things in concise ways.
I also believe that DSLs will penetrate through all the layers of our systems, all the way to the bottom.
This is a huge change, but it has fascinating implications. To see a glimpse of this, check out the architecture of the brand new commercial product Datomic. It’s a functional database that keeps a full fidelity history of every change, allowing you to roll the database back in time to see snapshots of the past. In other words, an update doesn’t destroy data; it creates a new version of it. Once you grok the implications of that, you may be answering to your boss about why you are destroying valuable historical trending data every time you update a record in your relational database. One cool Datomic use case: because you always have history, practices like Continuous Delivery, which relies on the ability to roll your database backwards and forwards in time, become trivial. Now, with relational databases, you use tools like Liquibase that have complex scripts to sync schema and data changes (best), you use snapshots to restore to known good restore points (just OK), or you do it manually (the horror!). Using an immutable database, you just move the time pointer backwards. Testing multiple versions of your application becomes trivial because you can directly synchronize schema and code changes.
Datomic is built with Clojure, assuming functional constructs at the architectural level, and top of stack implications are amazing.
Summary
Don’t believe people that tell you that dynamic languages are dangerous - too much evidence exists to the contrary. Rather, ask what makes them safe and make sure you apply that to all your development, regardless of language type.
Rather than stress about dynamic versus static, the much more interesting discussion now is functional versus imperative, and the implications of this change go deeper than the previous one. In the past, we’ve been designing imperatively using a variety of different languages. Switching to the functional style is a bigger shift than just learning a new syntax, but the beneficial effects can be profound.