Scalable computer programming languages

There will always be things we wish to say in our programs that in all known languages can only be said poorly. -- Alan Perlis

NOTE

I wrote this article a long time ago (in 2001, if memory serves), and frankly I'm not that interested in discussing it anymore. These days I'd rather write programs and teach programming languages than write opinion pieces. The article accurately reflects my opinions as of the time I wrote it, but may not accurately reflect my current opinions. I didn't post this article to reddit.com or to any other site. I have no interest in drumming up traffic to this page. I've received some surprisingly hostile emails from people who disagree with me. To those people: please realize that this is a rant, an opinion piece. If you don't agree with me, feel free to write your own opinion piece. But name-calling and trolling just makes you look bad and undercuts your own arguments. I won't respond to trolls, even if they also have some good points to make. If you can't talk civilly, talk to someone else. I am continually astonished by how angry comparative discussions of programming languages can get. Can't we all get along? There are more important things to get mad about, like cancer, world hunger, nuclear proliferation, etc. That said, I welcome constructive comments. In fact, I've received a number of insightful comments, and I've added some notes at the end summarizing some of the feedback I've received as well as some of the ways my views have shifted since I originally wrote this.

Introduction

brittle

computer programming languages

What is a scalable computer language?

Aspects of a programming language that affect scalability

Garbage collection: very, very good

I saw a rather stark example of this recently. One of the things I do professionally is teach the C programming language to Caltech undergraduates. I emphasized how important it was to always free memory that had been allocated. However, many of my students simply ignored me, and their code was littered with memory leaks. I got so tired of writing "this code has a memory leak here" on their assignments that I wrote a very simple memory leak checker. They are now required to write code that passes through the memory leak checker without any reported leaks before they submit their assignments. However, I was somewhat dismayed to find that my own answers to the assignments had a couple of subtle memory leaks as well! Since I have more than ten years of C programming experience, and have worked on several very large projects, this suggests to me that manual memory management is much harder than I'd previously supposed it to be.

There is a cost to GC, both in time and space efficiency. Well-designed garbage collectors (especially generational GC) can be extremely efficient (more efficient, for instance, than naive approaches such as reference counting). However, in order to do this they tend to have significantly greater space usages than programs without GC (I've heard estimates on the order of 50% more total space used). On the other hand, a program that leaks memory has the greatest space usage of all. I've wasted way too much of my life hunting down memory leaks in large C programs, and I have no interest in continuing to do so.

In conclusion, I would say that of all the items I'm discussing here, GC is the single most important one to ensure that a programming language is scalable. This is why programmers who move from a language without GC (say C++) to one of roughly equivalent abstractive power but with GC (say Java) invariably say how much happier they are now that they don't have to worry about memory management and can concentrate on the algorithms they're trying to write. Personally, I'd rather pull my own teeth out than write a large project in a language without GC.

Direct access to memory and pointer arithmetic: very bad

The scalability cost of pointers goes far beyond just making GC harder. Pointers (and especially pointer arithmetic) tend to destroy any safety guarantees you might want to be able to make about a program. It's not hard to see why. When you have a pointer to (say) an integer, and you can add 1,000,000 to that pointer and dereference some random region of memory which may or may not be part of your program's run-time image, all hell can break loose. If you're lucky, you'll just get a core dump and your program will terminate. If you're not so lucky, some part of the program memory will be corrupted, leading to mysterious bugs that are extremely difficult to track down, because they manifest themselves far away from where the original problem was. This leads to a huge increase in debugging times, which dramatically hurts programmer productivity. As the program gets larger, the opportunities for this kind of problem increase, which represents a significant barrier to scalability.

The usual argument in favor of direct pointer manipulations is that they make it possible to write faster code. This is often true; I've seen cases where using pointer arithmetic judiciously increased the speed of a program by a factor of five. However, the reverse is also often true; many program optimizations (normally performed automatically by the compiler) are rendered much more difficult or impossible in code that uses pointers. In other words, languages that enable micro-optimizations often make macro-optimizations impossible.

The author of the Eiffel language, Bertrand Meyer, has said that (I'm paraphrasing) "you can have pointer arithmetic, or you can have correct programs, but you can't have both". I agree with him. I think that direct memory access through pointers is the single biggest barrier to programming language scalability.

None of this is meant to imply that pointers and pointer arithmetic don't have their place; they are crucial for low-level close-to-the-metal programming. However, large programs written at a low level are simply not scalable. The right way to use languages like C is to implement small, focused low-level components of applications written primarily in higher-level languages. In fact, this is also the right way to use C++; you write some low-level classes that use pointers in some of their methods and then encapsulate these methods so you never have to expose the pointer manipulations to a user of the class. This would be nearly ideal, except that the compiler has no way to enforce this; you can always use pointers if you want to.

By the way, there is an interesting language called Cyclone which is essentially a "safe" variant of C. It has three different types of pointers, some of which allow pointer arithmetic and some of which don't. Pointer arithmetic in cyclone is always checked for safety. This language is thus much safer than C; the run-time cost varies from negligible to substantial. However, cyclone doesn't correct the other problems with C (described below), so I wouldn't describe it as particularly scalable.

Static type checking: mostly very good

Advantages of static type checking

In addition, static type checking leads to significantly faster code than dynamic (run-time) type checking. If the compiler knows that 'a' and 'b' both represent small integers, and it needs to compute 'a + b', then it can insert the code for addition of small integers right into the program. If the only thing that's known about 'a' and 'b' is that they represent objects that might be integers, or floats, or strings, then the decision on what to do has to be deferred until run time. Type checking at run time is expensive. Therefore, in addition to the other advantages of static typing, you also get faster code.

Type declarations vs. type inference

Foo foo = new Foo(); // Declare a new object of type Foo.

Type casting

type casting

Static vs. dynamic type checking

Another (weak) argument in favor of dynamic type checking is that dynamically typed languages tend to be much less verbose than statically typed ones, which means that the algorithm can often be expressed more clearly and succinctly. However, statically typed languages with type inference like (ahem) Ocaml circumvent this problem, as mentioned above.

If static type checking is just too restrictive for your tastes, then dynamically typed languages like Lisp, Scheme, Python, Ruby or Smalltalk are quite pleasant to program in, although my view is that the lack of static type checking hurts the scalability of these languages substantially. What typically happens in large projects written in these languages is that extensive unit tests are written to catch type errors as well as logical errors (the Smalltalk community has been particularly aggressive about promoting this approach; see Kent Beck's extreme programming site for much more on this, which is not limited to Smalltalk or to dynamically typed languages).

Another interesting approach is "soft typing", which is a cross between static type checking and dynamic type checking. Roughly speaking, it allows the same expressiveness as dynamic typing, but will statically check the types of everything that it can, and if it can't decide whether some program element is correctly typed at compile time it will check it at run time. This approach is used in the Dylan language (and also in Common Lisp, to a greater or lesser extent depending on the compiler) and is being actively developed from both the theoretical and practical standpoints by the PLT Scheme team of computer language researchers/implementors. I look forward to seeing what they come up with.

Exception handling: good

Effective C++

More Effective C++

Run-time error checking: good

Array bounds violations

Arithmetic errors

Assertions and contracts: very good

assert(i == 100);

Support for abstractions

Module systems: very good

Object-oriented programming: good

Functional programming: good

for

while

Many algorithms can be expressed much more concisely and elegantly.

Not using mutable data removes a large class of potential bugs.

Functional programs are much easier to verify for correctness than non-functional ones.

Higher-order functions (functions that take functions as input and/or return functions as output) can be used to factor out common programming idioms. This makes it easy to modify one kind of program to do related tasks.

Macros: mostly good

if

for

while

They can make debugging substantially more complicated.

They can in some cases make code harder to understand, because macros (at least in Lisp) look like functions but behave in a totally different manner.

Writing effective macros is non-trivial.

Components

Syntax and readability

The scalability of different programming languages

C

C combines all the power of assembly language with all the ease of use of assembly language. -- unknown I'd just like to take this moment to point out that C has all the expressive power of two dixie cups and a string. -- Jamie Zawinski, in the source code for xkeycaps There is a point in your life when you realize that you have written enough destructors, and have spent enough time tracking down a memory leak, and you have spend enough time tracking down memory corruption, and you have spent enough time using low-level insecure functions, and you have implemented way too many linked lists. -- Miguel de Icaza Greenspun's Tenth Rule of Programming: "Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified bug-ridden slow implementation of half of Common Lisp." -- Philip Greenspun Your superior intellect is no match for our puny weapons. -- unknown, via Aaron Stern

I'll be blunt: C is a horrible language for developing large projects in. C contains almost all of the bad language features described above and almost none of the good ones. Furthermore, some of the good features it does have (like static type checking) are so corrupted by type casting that they are much less useful than they would otherwise be. C also offers essentially no abstraction capabilities whatsoever except for arrays (which are really just pointers in disguise) and structs (ditto).

Does that mean that C is a useless language? Far from it. It has its place, and its place is a vital one. C is an excellent language for writing code that has to interface directly with the machine ("bare-metal" programming). It is also a good language for implementing better languages in ;-) The Ocaml runtime engine, for instance, is written in C (with some help from assembly language). C is useful for writing the 1% of your application that absolutely, positively, has to run as fast as possible. However, if you're trying to write a large application entirely in C you are in for a miserable time. Instead, you're better off picking a good language that has a C foreign-function interface (FFI), so you can "drop down" into C when you really need to (which hopefully won't be that often).

Some people (particularly novice programmers who have no idea what they're talking about) are under the illusion that because C offers such direct control over the machine, it's the only "real" programming language for "true hackers". If that were true, all "true hackers" (whatever that means) would be writing code in assembly language, which is much closer to the machine than C is. I hope some of the quotes above, some of which are from world-famous open-source hackers, will help to dispel this myth. If not, then I recommend that you try to write a really large program (> 100,000 lines of code) in C, and tell me what you think at the end of that experience. I think you'll find my arguments much more persuasive.

C++

C++ : an octopus made by nailing extra legs onto a dog. -- off smalltalk.org Think of C++ as an object-oriented assembly language. -- off the guile Scheme mailing list C makes it easy to shoot yourself in the foot. C++ makes it harder, but when you do, you blow your whole leg off. -- Bjarne Stroustrup Programming in C++ is premature optimization. -- off comp.lang.python

To be fair, I have to point out that there is some really neat stuff possible in C++ using templates (typically lumped under the name "template metaprogramming"; see the book Modern C++ Design by Andrei Alexandrescu for more information.). This is very much wizard-level programming (which is OK), but to my mind it doesn't even come close to compensating for the lack of GC. What would make C++ a more scalable language is (a) including a garbage collector as part of the standard library, and (b) having some way to restrict the use of pointers to particular modules that need direct pointer access (such as very low-level data structures). I'm not very optimistic that this will ever happen, but I hope it does.

Java and C#

The abstraction level of both C# and Java is mediocre; it's much better than C, somewhat weaker (!) than C++, and not nearly as good as languages that support both object-oriented and functional programming (such as Lisp and Ocaml). Therefore, I find programming in these languages to be pretty boring and tedious. Many of the scalability features in these languages are not, strictly speaking, part of the languages at all but of the environment(s) built up around the languages. For instance, the support for components, versioning, packaging and documentation generation are all features of the environments. I hope we will soon start to see these kinds of meta-features in better languages than Java or C#.

Eiffel

There is a very strong assertional system implementing "design by contract" (see above), which is totally incorporated into the object-oriented framework, so subclasses cannot break assertions of their superclasses (though there are theoretical arguments about whether this was done the right way).

Multiple inheritance is handled in what I consider a very simple and effective way; any naming conflicts must be resolved by renaming the inherited classes.

Inheritance can be controlled at a finer granularity than the private/protected/public modifiers seen in C++, Java, and C#.

Eiffel has a few deficiencies. The syntax is very verbose (there are something like 70 keywords) and also quite unfamiliar. The theory behind Eiffel rules out some language features that we have come to expect, like multiple exit points from loops. Many familiar features are found in a completely unfamiliar form. Classes are the only module units, which I think is abusing the notion of class (and which leads to the excessive use of multiple inheritance in situations in which it isn't really needed). These deficiencies are not show-stoppers, however.

More significantly, functional programming isn't supported (although the last time I checked something analogous to function pointers was in the process of being added to Eiffel; I'm not sure what the current situation is). Also, the type system has some holes in it that require link-time analysis to make sure that a program is correctly typed. This is a problem if you want to create shared libraries (although I admit I don't know all the details).

In conclusion, I would say that Eiffel is quite a scalable language, certainly more so than C++, Java, or C#. I personally find the lack of support for functional programming quite stifling, but I'd rather program in Eiffel than in Java, C++, or C# any day.

Python and Smalltalk

Perl

Python: executable pseudocode. Perl: executable line noise. -- off comp.lang.python

Parenthetically, one cool thing that has come out (actually, that is in the process of coming out) of the Perl 6 effort is an amazingly cool project called Parrot, which is a virtual machine targeted at dynamic languages. The goal is to have a common virtual machine for running Perl, Python, Ruby, Scheme etc. I like this project very much, so please check it out.

Common Lisp and Scheme

Most people are just too damn dumb to recognise how great Lisp is. -- off slashdot [Lisp] is the only computer language that is beautiful. -- Neal Stephenson The journey of a thousand miles begins with an open parenthesis. -- Rainer Joswig Will write code that writes code that writes code for food. -- Martin Rodgers Those who do not understand lisp are doomed to re-implement it. -- attributed to Erik Naggum, off comp.lang.lisp

Here are some of Lisp's interesting features.

It has an incredibly simple and unambiguous syntax (which is admittedly a little peculiar when you first encounter it).

It fully supports functional programming (and was the first language to do so).

It has garbage collection (and was the first language to have it).

There are no pointers in Lisp code.

There are no type declarations.

Structural macros are a fundamental part of the language, and because of the simple syntax are easier to write than in any other language.

The only serious drawback that Lisp has is that it relies mainly on dynamic typing. This is great for interactive exploration, but becomes an obstacle as the size of a program gets larger. Common Lisp at least allows type declarations, which the compiler is free to check for consistency (or to ignore). However, it's not easy to get the kind of absolute type safety in a Lisp program that you'd get in (say) an Ocaml program. Still, I really like Lisp and I think that every serious programmer needs to learn it. Also, at least one implementation of Scheme (the PLT Scheme implementation) promises to provide a usable type checker in the near future, which I look forward to.

Dylan

Objective CAML (Ocaml) and Standard ML

There is a joke about American engineers and French engineers. The American team brings a prototype to the French team. The French team's response is: "Well, it works fine in practice; but how will it hold up in theory?" -- unknown Perl, C, FORTRAN, Pascal and AWK are purely procedural (not even OO) languages. Functional languages are to large reflecting telescopes what these languages are to binoculars. -- off slashdot The best and the brightest in the programming languages field are working on functional programming languages. -- Bjarne Stroustrup (paraphrased from an interview)

big

What does Ocaml provide that makes it so great, and in particular makes it such a scalable language?

Garbage collection.

No pointers.

Extremely fast compilation to bytecode.

Compilation to extremely efficient native code (often faster than C++, often comparable to C speeds).

Static type checking with type inference (one of the few languages that has this; you can't imagine how cool this is until you use it).

Full support for functional programming.

Full support for imperative and object-oriented programming.

A great module system.

Polymorphically-typed functions (functions that are generic over entire classes of types).

Algebraic data types, which make creating complex data structures incredibly easy.

Functors (functions which create customized data types parameterized on an input type or set of types).

A programmable preprocessor (camlp4) that can be used to change the language syntax (!).

Having said that, I have to add that Ocaml is a long way from perfect. The syntax is a little weird in many places. The language has a fairly long learning curve. There is not enough good documentation and far too few books. The standard library is much smaller than what you'd find in (say) Java. On the other hand, none of these problems are show-stoppers, and I predict that Ocaml is going to become a major force in the world of programming languages in the next ten years, especially among very high-level hackers.

Social considerations

Ignorance

The learning curve

hate

think

In addition, the learning problem becomes much, much worse when the new language embodies a different programming paradigm (e.g. functional programming), even when the language also supports more familiar paradigms. I see this even in Caltech undergrads, who supposedly have the highest average SAT scores of any group of students in the world. Most of them already know how to program in C when they get here, and we teach them Scheme in a mostly-functional style that is totally unfamiliar to them. It's remarkable how much difficulty they have with this, big SAT scores notwithstanding.

Finally, it's true (but absolutely amazing to me) that small differences in syntax between computer languages can make it incredibly difficult for someone who has mastered one language to learn another. This is why Java and C# (correctly) adopted a C-like syntax; it's not that it's better, it's just more familiar and thus is less likely to be rejected. Programmers are an odd lot; they will often reject a language based on the most trivial criteria (e.g. whether or not the language uses semicolons to separate statements).

Misconceptions

All programming languages are basically the same.

Code written in anything but C is going to be incredibly slow.

Code written in any language with garbage collection is going to be incredibly slow.

"Real programmers" only write code in C.

I hope I've helped to dispel some of these misconceptions in the material I've written above.

Sacrificing everything for speed

Fortunately, the rise of Java and scripting languages, and the success of projects that have used those languages, have made the speed obsession much less of a factor than it used to be.

Sacrificing everything for bit-level control

must

Sacrificing everything for the lowest common denominator

It makes it much easier to find a job.

A popular language usually has a lot more libraries available.

It's much easier to find new programmers to work on a project if it's written in a language that a lot of people know.

The language is massively promoted by a company with a lot of money ( e.g. Java and Sun, C# and Microsoft).

Java and Sun, C# and Microsoft). The language spreads in a "grass-roots" fashion (C, C++, Perl, Python).

that

Conclusions and recommendations

garbage collection

no pointers or pointer arithmetic

a foreign function interface to the C language

static type checking with type inference

support for exception handling

run-time error checking for errors that can't be caught at compile time, like array bounds violations and division by zero

support for assertions and design by contract

a powerful, statically checked module system

support for object-oriented programming

support for functional programming

structural macros

support for components

a simple, consistent and readable syntax

If you've read this far and agree with many of my observations, then my recommendation to you is simple: learn some new languages. In particular, learn all of the languages that I've discussed above, even the ones I put down. Write sizable programs in all of these languages. This will not only be fun, but it will teach more about what makes a computer language scalable than you will ever learn by reading. And if you only want to learn one programming language out of all the ones I mentioned, please do yourself a favor and learn Ocaml. It's not perfect, but it's the best language I've ever used. If you have the time to learn two languages, the other language should be Common Lisp or Scheme.

Have fun! Epilogue: May 2003

Since writing the original version of this article, I've had some experiences which have modified some of my views. Most importantly, I am not as high on Ocaml as I was then. While I still think Ocaml is an outstanding language in many respects, there are significant things I don't like about it. I wanted to use Ocaml as the language for a large project I wanted to work on, but found very quickly that I couldn't use it the way I wanted. The object system doesn't support multimethods (which is no surprise; very few languages do), and there were certain features I wanted that called for multimethods. In C++ or Java, you can fake multimethods because you have run-time type identification (RTTI). But the Ocaml developers refuse to add this feature to Ocaml on quasi-religious grounds; the idea of having programs which cannot be proven to be type-safe at compile time goes against everything they believe in. No matter if the type violations give rise to a safe exception just like dividing by zero does; they won't hear of it. So I'm not using Ocaml for that project.

I've also had more experience with both Java and C++ since writing the first version of this article. I haven't changed my opinion of Java much; the best thing about it is that it comes with a huge variety of useful libraries. The upcoming "Tiger" release of Java will add several new language features which will make programming in Java much less painful (notably generics and autoboxing), which I welcome. I think Java is a fine language for many types of applications, but it doesn't have the qualities of coolness that make me fall in love with a language, and the abstraction level is still less than many other languages, including C++. Speaking of C++, I now understand better why C++ is the way it is. Even though C++ is monstrously complex, the ability to write code that moves smoothly from a bit-level of abstraction to a fairly high level of abstraction is extremely valuable for many projects, especially ones where efficiency is paramount. I intend to do more C++ programming and also to use the Boehm-Demers conservative GC to see how effective it is in combination with C++'s other features.

Martin Rodgers (author of one of the quotes above) sent me an email where he said that his main criterion for scalability is that a language not put a glass ceiling on abstraction. This is the classic Lisp programmer's viewpoint, and I am very sympathetic to it. One of the big challenges in programming language design is to get languages with the flexibility of Lisp but with more static guarantees. The ML family of languages are a compromise between static type safety and Lisp-like flexibility. I look forward to future developments in this area. Epilogue 2: July 2006

This article got posted to reddit.com and probably some other places, so I've been getting a lot of comments on it. Here is my response to some of the ones I found most interesting.

Julian Morrison sent me this email:

There's a very important feature you missed, and it's the real explanation for the success of Java: separable, atomic, pre-packaged, versioned functionality. Jarballs. Those, more than anything else, make reusability real. Java programming is about plugging together ready-built parts. Nothing else comes close.

I have to agree with him on this, and it's a major omission from the discussion above (the component stuff is sort of related to this). I don't find Java to be a very inspiring language, but I like the Java infrastructure a lot (the same comment applies to C#). With Java, you can download packages and have pretty good confidence that everything will work as it should (with some caveats that I'll mention below). There are a bunch of features of the Java infrastructure that make this possible: bytecode, having the same virtual platform on every real platform, versioning, metadata, etc. but they all result in a chunk of code which (ideally) "just works". In fact, it doesn't always "just work" but it "just works" more often than in most other languages I've used. To give an example, I tried to install a podcasting program which was written in Python, but which used Python interfaces to a number of libraries written in C or C++. I couldn't get it to work (and I've been doing this for a long time) because of all the version skew problems. This wasn't Python's fault as a language; it was the fact that the Python packages didn't track the C/C++ versions that were being used well enough. This is much less likely to happen in Java, for the reasons mentioned above and also because using code written in C/C++ as part of a Java program (i.e. through JNI) is less necessary (here's where JIT-compiling pays off big time).

On the other hand, my colleague Donnie Pinkston, who has forgotten more about Java than I'll ever know, has pointed out that it's very easy to get into classloader hell for projects that define their own classloaders (which apparently is often necessary), so it's not all a bed of roses. But I think there is a point to be made that Java gets some of the large-scale issues less wrong than most languages. These issues tend to be boring non-sexy things that don't excite my computer language sense, but they are absolutely essential in practice. If nobody can install your program, nobody can use it. And yes, I think I've spent as much time typing " configure;make;make install " as anyone, and I don't think that's good enough. Material for another rant.

One person mentioned that Common Lisp has multimethods, which is true (it's part of CLOS, the Common Lisp Object System). Multimethods are very cool (some languages, like Goo, build them in right from the start); my impression, though, is that they're hard to implement efficiently. One day I'll build a language with multimethods to find out for myself.

Someone argued that I'd been too hard on Ocaml for claiming that it's not able to fake multimethods. All I can say is: try it, and see how easy you think it is. I still think Ocaml is a great language, by the way.

One person argued for Lisp's scalability on a number of levels, which essentially comes down to Paul Graham's argument that in Lisp, you extend the language towards the problem instead of coding the problem down to the language (which I mention above). Two key features that enable this are a uniform syntax and syntactic macros. I'm definitely a fan of uniform syntax (for one thing, it lessens the conceptual load of learning the language) and of syntactic macros. However, macros have interesting and complex interactions with module systems, and many people feel that Common Lisp didn't get this right. Matthew Flatt's paper Compilable and Composable Macros goes into the PLT Scheme macro/module system, which is an interesting new approach to this problem that tries to be the best of both worlds. Also, there has been a lot of recent work on "multi-stage programming", which generalizes the notion of compile-time computation to an arbitrary number of stages (compile-time, run-time, link-time, whatever) and also can incorporate static type checking. This seems extremely promising to me. Tim Sheard has a number of papers on this and related subjects, as well as links to other work.

Julian Noble (an ex-Caltecher!) asked me about Forth, a language that he is intimately familiar with (he wrote a book called Scientific Forth on (you guessed it) scientific computing in Forth). Forth was my first love among programming languages (I read Leo Brodie's Starting Forth a long time ago), and I've had kind of a love/hate relationship with it ever since. You can do cool things in Forth that are nearly impossible to do in other languages (like change the lexical syntax), but it always seems to me that things that are fairly easy to do in other languages are hard to do in Forth. I could write a very long article explaining this in detail, and I probably will one day. Also, Forth has the "write-only" quality that I dislike about Perl, only even more so (though mitigated somewhat by the very uniform syntactic and semantic model). I've written two experimental languages to try to get at the essence of what I like about Forth without the things I don't like; I haven't gotten there yet. (By the way, writing Forth-like languages seems to be a rite of passage for computer language geeks.) I agree with Julian that Forth is a very easy language to debug, and also that it's a language that everyone with a serious interest in computer languages should study.

I pretty much stand by my statements on C++ that I expressed in the first epilogue. C++ is a terrifically powerful language, but it's extremely complex and there are so many ways to shoot yourself in the foot that it's hard to use. I've started to think of C++ as a giant macro system around C, which makes a lot of the design decisions easier to understand. Stan Lippman's book Inside the C++ Object Model is essential reading if you want to understand C++ better, though the book is somewhat out of date.

Since writing the last epilogue, I've gotten very enamored of Haskell, which I've been interested in for a long time. It was actually a couple of my students (Brandon Moore and Aaron Plattner) who turned me (back) on to Haskell. Haskell has come a long way in the last few years, and is now starting to be used for serious applications. I think Haskell has the potential to be the most scalable language ever, but the learning curve is monstrous. The great thing about Haskell is that its purely-functional nature allows you to combine components arbitrarily, and they always work the way you expect! Al Barr once described this to me as the "snap-together" quality of a language, and Haskell code snaps together very nicely. However, there's a cost; dealing with I/O and mutable state requires more work than in most languages (though the type system does give you nice guarantees about which functions can do I/O or state manipulation and which ones can't). Another interesting thing: the "point-free" style of programming in Haskell reminds me a lot of Forth! In Forth you create new functions by concatenating old functions; in Haskell you do the same thing, but with a function composition operator between the functions (of course, it's not _quite_ that simple, but it's close). So maybe I've come full circle: Haskell is Forth, lambda calculus is SKI combinators, and I need to up my medication ;-)

References

Go back to my home page. Last updated April 11, 2017

Mike Vanier (mvanier@cs.caltech.edu)