Languages That Get out of Your Way

A great debate in linguistics is how much language influences thought. In the world of computer science, I am firm believer in the theory. Paul Graham, amongst many others, has nicely argued the yes side of the debate.

Many developers start off in statically typed languages like I did. I learned to program using Pascal, did a bit of Assembly and then settled in with Pascal via Delphi. The first dynamic language I used was Magik – it was quite a shock. Since no one has ever heard of Magik, its a proprietary language used in the Smallworld GIS system that is quite similar to Ruby.

Magik had awful tools, no static type checking, no debugger, no GUI building tools, etc. And no compiler, at least not in the sense I was used to.

First Impressions Can Be Misleading

Needless to say, my first impressions were less than enthusiastic. Everyone kept telling me the environment was so much more productive, but I didn’t buy it. I could churn out object pascal almost as fast, and Delphi’s blazingly fast compiler and great tools made up for any difference. I suppose people were comparing to C++, where at the time you might as well have gone off and had several cups of coffee between each edit-compile-test cycle. Or maybe to toy languages like AML (another proprietary language from the GIS world).

Anyway, for beginners, the environment was just awful. The library documentation was non-existent – if you wanted to know what a method did, you went and read the source code. No sir, no fancy online hyper-linked context sensitive help files.

And the final nail in the coffin, the Magik IDE was an albatross called Emacs. Emacs drove me too such distraction I went off and wrote my own IDE (sorry, ten years haven’t changed my mind about Emacs, but I sure like VIM).

But I was paid to write Magik, so I wrote Magik. After a rough start, things started looking up a bit. It sure was nice not worrying about memory allocation and deallocation. And having an interactive console, where you could poke around inside a running program, that sure was neat. And then there were the truly weird things – like dynamically loading classes. Even better, you could replace methods in an existing class by simply redefining them in another file (we called it reopening classes, the term today is monkey patching). And you could pass functions as parameters via the use of procs which were closures – although I didn’t know that at the time.

Stuck With Complexity

Almost ten years later, I find myself mostly programming in Ruby, Javascript and C. Yikes, what happened? I’m as surprised as anyone.

My take is that contrary to popular wisdom, a good language gets out of your way and lets you do what you need to. This is quite counterintuitive. Computer programs are pinnacles of brittle complexity – one tiny mistake in millions of lines of code brings the whole edifice crashing down. The natural inclination is to make the walls of that edifice as thick and strong as possible. Java is a great example of this line of thought, you can see examples of it throughout its design:

  • Use of static type checking
  • Polymorphism only through inheritance of classes or interfaces
  • Final classes
  • The forced use of exception specifications
  • The forced handling of exception specifications
  • Difficulty in modifying code at runtime
  • Strong encapsulation
  • Clunky reflection

These things make the language less malleable. In return, the payoff should be more robust programs. But do you really get that? My experience is no, but I would love to hear about any references to studies or research that can provide a definitive answer either way.

Trusting my experience, I don’t believe programs written in Java (or C++, etc.) are on average more robust than programs written in Python, Ruby, Smalltalk, Perl, etc. So what has the loss of malleability cost you? Once again, my experience tells me quite a lot.

Given a reasonable sized program, I can guarantee you a few things:

  • It contains bugs
  • It’s used in ways the designers and developers never imagined
  • It’s execution environment is constantly changing

If you’re stuck with a brittle edifice of complexity you don’t want it to be a fortress complete with ten foot walls and surrounded by a serpent filled moat. What you want is a building with an open floor pan where you can nudge a wall here, add one there and remove one over there.

In more concrete terms, if code is buggy then you want to be able to write up a patch, throw it in a directory somewhere, and have the application load it automatically replacing the invalid code. Or closely related, you want to provide a simple mechanism to add in new functionality, just like Selenium does via its user user-extensions.js file.

Maybe you need to graft on a major piece of new functionality, such as adding support for serializing objects to JSON. One approach is to open up the base Object class and add a new method, toJSON, just as Rails 1.1 did.

Or let’s say you find yourself typing in the same boilerplate code over and over. Why not write a method that tells the language to do this for you? Ruby and Rails are filled with this type of metaprogramming, just as Magik is and of course the granddaddy of the technique, Lisp. Soon you’re on the road to creating your own domain specific languages, one of the hot topics du jour.

Or maybe you need to retrofit Object X so that it can be processed by Method A which expects to be passed an Object A. For some reason, Object X cannot inherit from Object A. So instead you leverage duck typing to add the needed methods to Object X.

These things are easy to do in some language, hard in others, and impossible in others.

Really, I Know What I’m Doing

Mastering a skill requires mastering its tools – be it construction, sword fighting, cooking, bike riding, flying, etc. The techniques above are some of the sharp tools of programming. You can use them to quickly make mince-meat out of your problem – or, on not so good days, mince meat out of your fingers. But when you make dinner tonight, I’m guessing you’re not reaching for the dullest knife in the drawer.

  1. Ryan
    July 23, 2007

    First, the whole “sharp knife vs. dull knife” analogy is beyond tired at this point, not to mention that it never actually meant anything to begin with (e.g. Java is a far “sharper knife” than Ruby because you can create a far wider range of applications with it that will run well on any major platform).

    Second, you say “Trusting my experience, I don’t believe programs written in Java (or C++, etc.) are on average more robust than programs written in Python, Ruby, Smalltalk, Perl, etc.”

    Well, trusting my own experience, programs written in Java are far more robust, maintainable and suitable for team development than programs written in dynamic languages. That’s why many large-scale and mission critical applications continue to be written in Java while next to none are written in, for instance, Ruby. Ruby doesn’t seem to be making any inroads into traditional Java territory any more than PHP has – and there’s no reason that it should because it’s only advantages come at an unacceptable cost (primarily long-term maintainability).

    Third, you mention a “brittle edifice of complexity” and I guess you are implying that Java applications fit that description(?). In fact, the statically typed nature of Java is what allows you to make massive, sweeping changes to an application with total confidence. Many of the refactoring capabilities that I use every day in Eclipse are simply not possible with a dynamic language. So I would argue that a typical Java application is more flexible than a typical application written in a dynamic language. If you do end up with a “brittle edifice of complexity”, it is most likely your design that’s at fault rather than the language.

    I guess my main point here is that, while it’s nice for you to share your opinion, you didn’t actually bring up any points to support it. You attempt to turn the world on its head by suggesting that Java is no more robust than dynamic languages because you haven’t seen an exhaustive study to prove it. The obvious problem with this line of thinking is that Java has already been proven to be an extremely robust and highly maintainable programming language. This is borne out by the numerous, large scale applications written in Java that are running and depended upon every day by countless businesses. Ruby has few, if any, such success stories. So it is obviously up to the Ruby advocates to prove that their pet language can scale, in terms of complexity and team development, to the same point that Java has for many years now — not the other way around.

    Another point I always feel compelled to bring up in these discussions is that the Java world has already adopted many of the supposed advantages of dynamic languages via higher-level, lighter weight frameworks and tools. For example, I have yet to see a Rails enthusiast who is also familiar with the Cayenne and Wicket frameworks, which will give you about the same overall level of productivity as rails but with much cleaner and more maintainable code.

    I think dynamic languages are certainly more well suited to certain tasks than static languages like Java. I just don’t think that large-scale, robust applications are their sweet spot.

  2. Brian
    July 23, 2007

    Wow Ryan. Such verbosity in your arguments. It reminds me why I don’t really like Java.

    Regarding your comment on refactoring tools, I only need to quote Steve Yegge.
    “I can’t tell you how many times I’ve heard people say they wouldn’t use Ruby because it lacks automated refactoring tools. Ruby doesn’t actually need them in the way Java does; it’s like refusing to switch to an electric car because there’s no place to put the gasoline. But programmers are a stubborn bunch, and to win them over you have to give them what they think they want.”

    Ooh, and btw, in my experience, its not the language, but the quality of developers that make programs better. Ruby just allows people to be much more productive.

  3. Charlie Savage –
    July 23, 2007

    Hi Ryan,

    Wow – that was quite a reply. Sorry you didn’t like the analogy, but ok.

    Anyway, like I wrote, Magik was the first dynamic language I learned. Its the basis for Smallworld, which is a GIS software package used by almost every large utility in Europe, and many in North America, plus a number of telecoms, to manage their networks. So the argument that dynamic languages aren’t used in mission critical applications is incorrect.

    As far as a “brittle edifice” – I think every program ever written is a brittle edifice, regardless of the language used. But of course many applications, particularly enterprise applications, have to evolve over time.

    Which brings us to your point of refactoring. An often cited advantage of static languages is that they make these types of changes easier. But I think its an illusion – making sure your types are correct certainly does not imply that your program is correct.

    The only effective way I’ve seen of making change less painful is a full test suite – unit tests, functional tests, etc – and even then you never know if you’ve done something wrong. Neither static languages nor dynamic languages have any advantages here.

    Where dynamic languages do have advantage is that you have more flexibility to change existing programs. If a method takes a Foo, you can give it a Bar, and it works via duck typing. And I cited a few other examples in the article.

    But many of these techniques are controversial – in fact, I thought they were nuts when I first learned them. But over time, and with needing them enough times, I’ve come around to see their value. And at this point I think they offer more value than static typing does, and thus, the reason to write the article.

    But that is just my opinion. One of the frustrations of these discussions is that there are no rigorous studies that I know of that show some language to be better suited for some task than others. Of course, maybe there never will be, since I haven’t got a clue as how you would perform such a study.

    Anyway, Java obviously works for Enterprise applications because, like you say, you can point out many of them. But does that make it better for them? Who knows. I could make the same argument for C++. Or Fortran. Or Cobol. Or Smalltalk. Or…well you get the point.

    Anyway, thanks for taking the time to write such a lengthy comment.

  4. Static
    July 23, 2007

    Look at the average PHP codebase (MediaWiki for example) to see some atrocious examples of happens to a large program co-written by many authors in a dynamic language.

    No one would argue that Google is devoid of highly skilled hacker types, yet Google’s is dominated by static languages (C++, Java) Google’s Java codebase easily dwarfs the entire Ruby repository, and there is less fear that a small change in one module will end up breaking all of their web properties, due not only to unit tests, but also to compile time type checks — which can be viewed as nothing more than a form of elegantly expressed declarative unit test constraints in that regard.

    Finally, the typical fanboys who discover dynamic languages and become new evangelists for them, writing blog pieces like this, commit the either-or fallacy and focus way too heavily on attacking Java or C++. The fact is, metaprogramming, closures, et al, are not mutally exclusive with static type systems.

    I can achieve almost everything Ruby addicts blab on about using Haskell or ML derivatives, etc. Type inferencing saves most of the effort required to declare types while maintaining strict typing but leaving methods generic. Thus, I can declare a typesafe method “add(x,y)” that will take a Foo or Bar, but I avoid the pitfalls of duck typing (and believe me, there are plenty of them)

    Haskell’s type system, ability to define arbitrary prefix/infix/postfix operators, pass functions as first class, allows efficient creation of DSLs, in fact, Haskell is better at DSLs than Ruby is. And if you need more power, you can try Template Haskell.

    Of course, Haskell isn’t mainstream, but the point is, this static vs dynamic debate is too polarized by fanboys on both sides.

  5. Charlie Savage –
    July 24, 2007

    Hi Static (and others)

    Obviously I didn’t do a very good job of writing this article because I think its main point has has been misunderstood. So let me try again.

    The post isn’t meant as a static versus dynamic language argument. Instead, its about languages that limit a programmer’s freedom in the belief that such limitations lead to safer programs.

    Static versus dynamic typing is just one piece of that puzzle – I also listed a number of other pieces in the article.

    I find that languages that have such restrictions are limiting. I used Java as an example because it has all of these limitations, not because it is statically typed.

    Static mentioned Haskell as a language the proves my post incorrect. I don’t think so. I’ve only programmed in Haskell for fun, to learn it, but from my little bit of expierence it doesn’t suffer from any of the problem I mentioned. So it qualifies as a language that “gets out of your way.”

    And I find Haskell’s inferred static typing to be fascinating (just as I find C++ templates to be fascinating). Perhaps it does offer the best of both worlds – but until I write a full program in Haskell, I don’t understand it well enough to know.

    Anyway, I hope this makes my thinking a bit more clear.