Wednesday, February 8, 2012

The Design of Hammer: Part III

When I took a course on type systems and programming languages, our professor drilled into our heads the actual meaning of the word "variable". I have decided to stick with this definition in the design of Hammer, for several reasons.

As mentioned in a previous post, the nature of systems programming demands that the applications it produces must adhere as closely as humanly possible to their specifications. Any bugs that exist can and will manifest sooner or later (and probably sooner than you think). A web server, for example, must be able to stay online continuously for days, weeks, or even months without crashing. A computer game, on the other hand, can crash routinely without much consequence (as long as the player remembers to save early and save often).

Thus, when faced with a trade-off between safety and convenience, Hammer will always err on the side of safety. Convenience is useful when you're doing exploratory programming or trying to implement a proof of concept. But, in the end, a program will be written only once but run countless times. In the long run, having to write a few extra lines of code is but a small price to pay for a better result.

Unrestricted mutability is one of those conveniences that Hammer will sacrifice. First, the downsides:
  • Any "global variable" will be harder to represent.
  • Efficient loops will take some ingenuity to fully implement.
  • Lazy evaluation will require syntactic sugar (to be elaborated in a later post).
Note that removing mutability didn't cause anything to become impossible, only slightly more wordy to implement. Bit-twiddling is still on the menu. The important thing to note is that Hammer, in exchange, gains the following very desirable properties:
  • As explained in the previous post, treating all variables as immutable enables a simple implementation of closures without automatic memory management.
  • The type of a function fully encodes everything that that function will do. The programmer can rely on types as documentation.
  • The type system becomes far simpler because it is easier to encode mutability in an initially immutable system than the other way around. C and its descendants have to struggle with complex rules regarding the propagation of consts and finals; Hammer, like ML and Haskell, handles the situation naturally via simple value semantics.
  • The extra guarantees that come from immutability facilitate optimization. When there is no such thing as aliasing, and the equivalent of GCC's __pure__ attribute is applied to almost every function, the compiler gets a blank check to do inlining and subexpression elimination.
  • Immutability makes implementing concurrency easier. Arguably, 99% of the pitfalls described in this paper do not apply. This point will be discussed in yet another post.
One might argue that removing mutability causes the programming language to move away from being a faithful representation of the underlying machine. To that, I have two responses:
  1. Programming languages are primarily for humans to use. If you want a language that reflects the machine, go code in raw assembly. The only thing that matters is that the result is fast.
  2. Modern machines are far more complicated than the simple five-stage pipeline you learned about in your computer architecture course. I think that immutability fits the out-of-order and highly concurrent nature of a recent multicore processor even better than something like the old C abstract machine.
And, of course, the above is pure opinion. I am open to discussion.

No comments: