Sunday 16 August 2009

Simple patterns for better arguments about programming languages

I somehow get myself into a lot of arguments about programming languages. Perhaps it's because I talk about them a lot, and everybody has an opinion!

Getting into arguments about programming languages generally means listening to a large number of value-based judgements until somebody gets bored and admits that language X really isn't as bad as he or she originally asserted, and yes, maybe language Y does have some flaws. This is largely counter-productive. If we're going to argue about the best way to skin a cat*, let's argue about things we can actually compare. So I present in this post a number of more objective ways of talking about the merits of programming languages such that you might actually persuade somebody that your well-informed opinion is actually valid!

(* Please don't, that's cruel and horrible)

I often hear the word "powerful" used to describe the relevant merits of programming languages, as in "People use Ruby because it is more powerful". This is often used in conjunction with lots of other words like "maintainable", "simple", "faster", "easier" etc etc. These are all largely intangible qualities that could just as well be described by "I prefer language X".

That said, maybe there is some value in comparing languages. So what can and can't we measure?

It's worth creating a distinction at this point between the programming language itself (i.e. syntax and semantics - the set of valid programs), and the eco-system that exists around the language, in terms of compilers, tools, libraries, user-bases etc. Sometimes these overlap in a big way, especially in cases where there exists only a single implementation of a given language.

Comparison 1: Performance

We have some reasonably good metrics for "performance" of a given implementation for a certain application. These are often pulled out when one party in a heated discussion about the merits of a programming language demands evidence to support claims of superiority or inferiority. That said, as a metric for comparing languages, they are pretty lousy. Sure, it's interesting that you can write a compiler for some language X that outperforms a compiler for language Y for a certain application. Big deal. It might say something about the ability for the program to express efficient solutions to problems, or it might just tell you that one compiler is better than another.

Before I wave my hands and dismiss "performance" as a useful metric altogether, it is true that some languages lend themselves to efficient implementations better than others. The Computer Language Shootout Game makes for interesting reading, even if it is not a completely scientific means of comparing whole languages. Without beating up on anyone's favourite languages, languages that permit dynamic behaviour (usually requiring interpretation rather than compilation-proper) don't really lend themselves to static analysis and optimisation. Sure, it happens, but it is added to the runtime cost of the application (because it is done, by necessity, at runtime). I don't think anybody would really have a problem admitting that languages like PHP, Python, Perl and Ruby are unlikely to be considered in the same performance category as C/C++, ML, Fortran and a bunch of other languages that do lend themselves to good compile-time optimisation and compilation to fast, native code (or something with comparable performance).

So, take-home talking point number one: Language X lends itself to compile-time optimisation and efficient code-generation that is better than language Y's.

You need to be careful with this one though - just because people haven't written good optimising compilers for a language doesn't mean it's not possible or even difficult! Sometimes it's just not a priority. If you're going to use this talking point, you need to be able to substantiate it! Think about things like: presence (or absence) of very dynamic features, referential transparency, treatment of constants, and syntactic ambiguity resolvable only at runtime (Perl, I'm looking at you).

Comparison 2: Readability

This is argument is abused regularly. Arguments about readability often boil down to "Language X is different to language Y, which I am used to, therefore I find it harder to read!". This is not a good argument. You're basically just saying "I find it easier to use things that I am familiar with", which is uh, pretty obvious, really. Some other person who uses X more than Y probably finds Y equally unreadable.

There might be one tiny point for comparison here, although I would hesitate to use it unless you're very sure that you're correct. It basically goes like this:

How much information about the expected run-time behaviour of a program can I discern from the structure of a small piece of the program?

To elaborate, what is the smallestt piece of the program that I can look at independent of the rest while still having at least some idea what it is meant to do? Can I look at just one line? A few lines? A page of code? The entire program spread over multiple files? This is probably equivalent to some kind of modularity property, although I don't mean this in terms of objects or module systems. I mean, "if something is true within this piece of code now, will it still be true no matter what else I add to the system?"

I figure this corresponds to "the amount of things you need to keep in your head" in order to read code. Smaller is probably better.

Talking point two: Language X requires the programmer to mentally store fewer things in order to read and understand the program than language Y.

You'll need to be able to substantiate this if you're going to use it. Think about things like scoping rules, syntactically significant names and consistency. Does this operator always mean the same thing? No? Does its meaning vary in some consistent way that is indicated by a (relatively small) context? It doesn't? Oh dear.

Comparison 3: Bugs!

Don't get drawn into this one lightly. Discussions about bugs can go on for days. The sources of bugs, the causes of bugs, and what language design decisions influence the ability to detect (and indeed prevent) bugs.

Being a formal methods geek, I'm pretty passionate about this point. I think bugs in all their forms are bad things. Some people see them as an unavoidable side-effect of producing software, much like mopping a floor will make the floor all wet.

I think a well-designed language should allow a compiler to prevent you from making dumb mistakes. I'm going to beat up on PHP here, because really, anybody who has used PHP pretty much loathes it. My pet peeve:


$someVeryComplicatedVariableName = doSomeStuff();

print $someVeryComplicatedName;

Whoops! I guess I didn't type that variable name correctly the second time. Oh well, don't worry, PHP's default behaviour is to initialise it to an empty value that is coerced to an appropriate type for the context. In this case, my program would print nothing at all. Sure, it issues a notice somewhere, but this isn't considered an error in PHP, because we don't declare things! In this case, the design of the language prevents the compiler/intepreter from stopping us from doing something very dumb.

Languages that lend themselves to compilers that enforce global consistency in some way are good at preventing errors. I've already said in a previous post that I'm a fan of type systems that prevent dumb mistakes. Obviously in a programming language there is always some conceptual gap between what we expect the compiler to do, and what it actually does. To me a well-designed programming language is one that will prevent me from being inconsistent, and which will tell me if my understanding of a program differs significantly from the compiler's interpretation of it (manifested in the way I am writing my program, probably as a type error!).

Finally, how many things are put in front of me that I have to get right in order for my program to be correct? Oh, I have to do my own memory management? So it would be bad if I got that wrong, right?

Talking point: Language X is designed in such a way that a compiler or interpreter can detect and report a larger class of errors, and prevent more simple mistakes than one for language Y.

Do your research on this one too. Unfortunately there are very few studies with substantial findings on the types of errors that programmers make. You'll need to restrict yourself to tangible classes of errors that are detectable in language X, but will not be detectable in language Y, by virtue of the design of the language. Remember, a bad compiler might not report errors, but that doesn't necessarily mean it's impossible to detect them!

The natural counter to argument is usually to propose an absurdly complicated way by which it might be possible to simulate the language-based features of language X in language Y at the application level. For example, "We'll search-and-replace to replace every occurence of variable x with a function that validates its internal structure!" At this point, you should probably just sigh and walk away, safe in the knowledge that you don't need to worry about any such absurd solutions in your own daily life.

Comparison 4: Eco-System Factors

If you want to argue about libraries and user communities, be my guest. If you're having an argument about programming languages, these things are probably important, but they really only tell us the way things are, not why they are like that or how they should be. Arguing about how things should be is not particularly helpful, but similarly, "Everyone uses X so it must be better than Y" is a very unhelpful argument. A lot of people get cancer, too. Doesn't mean I'm lining up for it.

Talking point: Language X has considerably more library support for than language Y, and that is what I am interested in doing.

Perfectly valid. Just don't fall into the trap of suggesting that having more libraries means that one language is better than any other. Most languages worth their salt actually provide some sort of foreign function interface, so the idea of a library being completely unavailable in a language is pretty rare. You might have to put a bit of work into it, but it's certainly not a limitation of the language just because someone wrote a library for X and not for Y.

Comparison 5: Non-comparisons

This is my "please avoid" list. If you use any of these arguments in a discussion on programming languages, you should really look at what evidence you have, because you probably don't have any:

  • Lines of Code - please, please avoid using this. It's just not relevant. If you use this, you have reduced an argument about the relative merits of programming languages to one about typing speed.
  • Elegance - I know what you mean, some solutions are elegant, but this really can't be quantified. It's probably the solution that is elegant, rather than the solution in a given language. It could probably be expressed elegantly in almost any language by a programmer sufficiently familiar with the conventions of that language.
  • "I don't need a language-based solution for that, we have social conventions". No, no, no. What you're saying here is that you are accepting that this problem is still a problem if people don't follow the conventions. Guess what? People are really bad at following rules. Some people are just born rule-breakers.
  • "Language X is too restrictive". Really? I find those pesky laws about not going on murderous rampages rather restrictive too. If you can't qualify this statement with a very specific example of something that is both within the domain of the language, and which genuinely can't be done using the features of the language in question, then you're just saying "Language X doesn't let me do things exactly how I did them in language Y".
So I hope this all provides some food for thought. I would love to hear of any other arguments that people resort to about programming languages that really annoy you. Or perhaps I've missed some genuinely valuable comparisons that can be made?

8 comments:

  1. Lines of Code - please, please avoid using this. It's just not relevant.

    I strongly disagree with that. But nobody cares what I think, how about Steve Yegge? In particular I believe, quite staunchly I might add, that the worst thing that can happen to a code base is size..

    ReplyDelete
  2. I agree somewhat with Josh. Lines of code can be a useful metric - the more verbose something is the harder it may be to understand. Sure it's rough, but it doesn't mean it's not worth considering. I guess though Gian's point is that we should be more specific in examining how/why things are more/less readable.

    Oh and Josh, I happen to know that mentioning Steve Yegge is not the quickest way to Gian's heart. :-)

    ReplyDelete
  3. Oh dear. Yeah, I can't say I find myself agreeing with Steve Yegge all that often. I mean, I'm sure he's a really smart guy and all that, but uh...

    Comparisons of LoC *between* languages are what I was referring to specifically. Sure, all other things being equal, if you write two python programs that do the same thing and one is shorter than the other, it is probably the superior one. If we're talking about Java vs. Python, or X vs Y, or whatever, I stand by my assertion that we're back to an argument about typing speed. Let me elaborate:

    I'm a better programmer than you. A bold assertion, I know, but you see, I type really, really fast. Therefore I can type more lines of code than you in the same amount of time. Conversely, maybe you're aa better programmer than me, because you type slower, and therefore you produce less code (which is better according to our good friend Steve Yegge).

    Of course this is complete bullshit, but it's to demonstrate the absurdity of using LoC to measure just about anything. If you really feel like you *need* a metric that measures "complexity", can't we do better than LoC? Especially for cross-language comparisons? I suggest counting occurences of the letter "k" in the source code. That's really no less arbitrary.

    and David, how do you know about my long-standing disregard for everything Steve Yegge says about programming? Have I said something about this before?

    ReplyDelete
  4. Maybe the "lines of code" argument is irrelevant because it can be split and merged into the "readability" and the "classes of error" arguments.

    When arguing about LOC, we often think about loop vs map and fold, classes vs closures, malloc() and free() vs garbage collection. In each case, the former is likely to be more verbose than the latter. The reason why differ: "classes vs closures" means more or less boilerplate, while "malloc() vs GC" means more or less error prone code.

    So, LOC is important. Not by itself, but as a magnifier: it may hint at genuinely relevant issues.

    ReplyDelete
  5. Loup, I think I agree with that in general. Certainly language-based features that are equivalent to pieces of code that would otherwise need to be written by hand have the capability to reduce instances of certain types of errors. That said, I'm not sure about the "readability" argument, because it may be such that one then needs to mentally store more information about the expected behaviour of that built-in feature. I'm definitely in favour of the use of things like map rather than manually constructing a loop.

    I was mostly getting at arguments around boilerplate though. I don't think boilerplate code is necessarily as evil as people make out, and this is often the argument used to say "Java code is 10 trillion times longer than Python code!" or similar. Errors in boilerplate code are usually restricted to syntax errors, which are trivially detectable at compile time (or at least at design-time) by almost every language in common use today.

    If the boilerplate code provides useful clues to the programmer about the expected usage or context in which a piece of code exists, I think it is likely to aid readability, rather than reduce it.

    ReplyDelete
  6. I agree about the boilerplate.

    The "readability" was also about locality, though. When your code is more verbose, it is often more spread out as well. That can feel like coupling. "Closures vs classes" is a case in point —unless you use Java's anonymous innner classes.

    ReplyDelete
  7. Right, so we could possibly characterise that as "features that permit avoidance of unnecessary verbosity, aiding readability" as another point of comparison?

    ReplyDelete