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
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.