A Programmer in King Arthur's Court

Occasional commentary about software, software construction, and the business of software.

Tuesday, April 06, 2010

Classically, there have been two schools of language design: the Lisp approach and the Fortran approach.

The Lisp school starts with the pure mathematical notion of a function, and then develop everything around the idea of evaluating recursive functions. There are also functions that return other functions, and so on. In this model, explicit assignment of variable values is not absolutely necessary, because you have parameter binding and presumably solve the "tail recursion" problem etc. Adherents to the Lisp school insist that LISP is far and away the highest-level programming language that could ever exist on a machine, and that they only deal with an inconvenient, ugly, illegible, and very consistent syntax because they don't want to incur the costs of machine-parsing something more "complicated".

But this is not the path I mean to advocate.

The Fortran school builds successive layers of abstraction atop the bare machine, progressively inching the syntax and semantics closer to what seems natural for practical programmers to design practical programs in. The only trouble is that practical programmers are the ones that have already been using the lower-level languages that Fortran and its successors were meant to replace. Therefore, you have all these notions of what is practical in a computer programming language: the system should result in programs that execute quickly; structuring notions should be embedded in the grammar so they cannot be confused; and so forth.

This isn't quite the ideal either.

History has shown a certain amount of convergence of these two schools. But I think they are converging toward a third point, and it's that point I mean to advocate. In particular, we should:
1. design our languages so that it's easy to express and reason about our desired programs,
2. axiomatically define the language independently of its realization in a programming system, and
3. value an implementation by its fidelity to the standard -- not any "reference implementation".

The first step is really one of the great open problems in computing science. It's complicated in part by disagreement over which programs are worth expressing. But I have some opinions on the matter, and I believe that the expression of these opinions in the form of language definitions is the first step towards testing them. The second, of course, is trying the new languages out in a community. But to get participants, you pretty much need a practical realization of the language.

Possibly the greatest example of this approach to language design is given by Pascal. It was designed from first principles for the approach of stepwise design refinement and sensible reasoning about the workings of a program. It turned out to be excellent in an educational context for just that reason, and it was also a significant improvement over virtually everything that came after it. Sadly, politics and advertising eventually trumped Pascal's dominance in the hobby-programming space, so that it was replaced by the likes of C++ and Java.

Realizing a new programming language is not a trivial endeavor by any measure. It's quite a lot of work to get any significant portion of a new system up and running. But let us recognize some facts:
1. We can work in an existing very-high-level language just to get things off the ground.
2. We don't need amazing speed from a proof-of-concept prototype; we can optimize later.
3. When you have -- and understand -- the source code to the compiler or interpreter, then it's easy to add features as necessary or desirable.
4. If you have a working interpreter for the interesting subset of your language, then you're really only a few steps from a working compiler. The difference is generating code to compute results instead of computing results directly. The complexity of generating that code depends on a lot of factors, though; it can be a major effort to make the conversion, but certain object-oriented methods can help the process along.

In any event, a programming language needs at least seven things to be "real":

input/output
mathematical and symbolic manipulation
temporal grouping of operations
selection of one possible operation out of more options
bounded repetition
functional/procedural abstraction
data abstraction

Of these, the last two seem the most difficult to realize because they have the greatest impact on how all the other parts fit together. But is there a way to factor this difficulty out of the way, so that we can change (for example) the mechanics of the data abstraction mechanism without (too much) changing everything else?

If so, then it becomes possible to develop a straw-man programming system and then successively improve upon its faults until you have the sort of programming system that people would like to use in the development of serious software projects.


0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home