I know quite a few programming languages and I realized that many of them are redundant. Perl / Python / Ruby / PHP / other dynamic, weakly typed scripting languages can all do the same things. How does one choose? If I’m working on a project with other people, what reason can I give to end the religious battle and just get things done? And should I come across another one in the near future, how might I make the decision?
So I set a new yardstick: I won’t touch a language that doesn’t support closures, and I won’t use a language that’s opinionated about something silly. The first implies two important things for me:
- The language has higher-order, first class, anonymous functions;
- The language supports lexical scoping and It Works (tm).
Python has some scoping issues and anonymous functions can only be on one line. It’s a cheap shot but it allows me to eliminate one redundant language since there’s no reason to use a language that has problems when I can use a language which doesn’t have them.
Java can emulate closures insofar as closures are a poor man’s objects; but by the same token, objects are a poor man’s closures and sometimes I don’t want to spend hours refactoring my code around a forced data model; this leads to AbstractFactoryClassGeneratorFactory classes or whatever bullshit you see in the JDK. Ruby has the same problem by virtue of its strong opinion which segues nicely to my second point!
Ruby and Java are “object oriented” which means everything is an object instance of a class. Which is great except that Alan Kay, the inventor of OO, has something to say on the subject:
OOP to me means only messaging, local retention and
protection and hiding of state-process, and extreme
late binding of all things.
C++ and Java immediately miss the boat here because they do not operate based on message passing but on calling a function (which is bound at compile-time; double whammy). The important distinction is that with late binding my system can theoretically upgrade in place or change behavior while running. Objective-C does get all of this correct (being influenced by Smalltalk) and you can do some nifty tricks called “method swizzling” if you’re into that sort of thing. More on C in a bit.
More to the point, Kay envisions OO as a style and not a language requirement per se. Ruby, Java, and other “OO only” languages miss these aspects to varying degrees. Any language which has a strong opinion about something which it doesn’t understand can be safely eliminated. Again, it’s a yard stick to help me eliminate quickly and not leave myself thinking of what could have been.
What about C? C is a hardware language which I group separately from other higher level languages. It’s a mechanism to manipulate memory and shuffle bits around more than anything else. So in a sense, it supports all these things. I can pass around function pointers, I have block level scoping, and I can write things as statelessly as I want. Hell, I can even go so far as to … implement all these other languages in it. See my point? Use C when you’re programming hardware, not when you want mathematical guarantees or elegance; you break out your Hoare triples for that. Objective-C is a thin layer over it providing Smalltalk style OO if you choose to use it. So C is an orthogonal discussion.
What of Haskell, then? Haskell has a very strong opinion about statelessness, referential transparency, static typing, and lazy evaluation (as in, the opinion is absolutely yes). However, when designing large systems (be they object oriented or otherwise), you want referential transparency and you want minimal (or non-existent) shared state. When you don’t share state and functions are referentially transparent, you can build pieces in isolation and run them concurrently with minimal issues. Erlang is functional and similar in spirit not for its own sake but because that’s the best way to achieve fault-tolerance in a real time system. It has its use case where I’d go for it.
As far as the strong opinions on static typing and lazy evaluation go, I think they’re justified. Haskell’s type system is where you sneakily hide state in a stateless language - but through this mechanism the state you do have is type checked and will be reasonably safe if it compiles at all. Lazy evaluation is the most contentious but you can also get around it if need be. So the opinion isn’t that strong. Plenty of standard and popular data structures evaluate strictly.
Curiously, as I ramble on I’ve noticed something about my preferences:
I like functional programming (referential transparency, shared nothing, asynchronous communication);
I like closures;
I like Smalltalk style OO.
Ideally, you treat “functional” and “object oriented” as styles, not language features. Ideally, in a big system, you could mix these styles. High level application logic is purely functional and you would simply use closures with mutable lexical environments to encapsulate state - and since objects are just functions, it’s all still functional with all the wonderful implications of that style; and since functions are first class values, you can share this state by communicating and not communicate by sharing state. Ideally you could have late binding and message passing (or something semantically equivalent) to get the most out of the OO style.
Ideally, you’d use Lisp since it has this mindset and supports alle these things. Programmers, start your flame throwers.