Archive for the 'programming languages' Category

Multiple Dispatch and Open Classes

July 13, 2009

In my last post I wrote that Scheme lets you extend the base language in powerful ways. In this post I’ll give you a concrete example of such an extension: TinyClos.

TinyClos is a minimal implementation in Scheme of Common Lisp’s object system CLOS. CLOS is different in many ways from the object systems of C++ and Java that most of us are familiar with. Two big differences are multiple dispatch and open classes.

Before I explain what multiple dispatch is I must explain what dispatch refers to. When you make a function call in any language, the compiler or language runtime must figure out which function the call refers to. This is called binding the function to an implementation. In some languages binding always occurs at compile time, in others always at runtime, and in still others sometimes at runtime and sometimes at compile time.

Some languages, such as C++, allow you to declare many functions with the same name as long as they have different parameter lists. Not only the name of the function but also the number of arguments and their types are used when binding. This is called overloading in C++ terminology. However, overloading is a compile-time mechanism, and as such only looks at the static types of the arguments. If you want to bind the call based on the run-time type of an object you have to make the function virtual.

However, virtual functions in C++ (and most other mainstream languages) only take the run-time type of the first parameter into account–the “this” pointer. This is called single dispatch, because it dispatches the call based on a single argument. As you can probably guess by now, multiple dispatch is when the run-time types of multiple arguments is used for binding. In other words, multiple dispatch is like overloading but with run-time types.

This is immensely useful in some situations, because you can decide what to do based on combinations of types, without having to sprinkle your code with instance-of tests or resorting to ugly work-arounds like the visitor pattern. For instance, to express that anti-aircraft units can attack air units but not tanks or other units types, I just specify a method can-fire-at? for the type combination (AirDefenseUnit, AirUnit) and another one for (AirDefenseUnit, Unit). The method with the most specific matching argument list will be selected, at run time.

Here’s another simple example in the syntax Bjarne Stroustrup wants for C++ (not included in the coming update of the standard):

bool canShareHex(virtual Unit u1, virtual Unit u2) {
  return true;
}
bool canShareHex(virtual GroundUnit u1, virtual GroundUnit u2) {
  return false;
}
bool canShareHex(virtual AirUnit u1, virtual AirUnit u2) {
  return false;
}
bool canShareHex(virtual SeaUnit u1, virtual SeaUnit u2) {
  return false;
}

In other words, GroundUnits can share hex with all units but other GroundUnits. This example also demonstrates the other feature I wanted to write about: open class extensions. As you can see above, the methods were not part of a class but added separately. This too is very useful, because you can’t always anticipate what people may want to do with your classes, and sometimes it’s impractical to cram everything into one class.

For example, let’s say you are writing a compiler and define classes for different kinds of expressions, like addition and multiplication and so on, reflecting the tree structure of arithmetic expressions. Since the set of operations (optimizations) you may want to perform on the tree is open ended, you wouldn’t want to make them all methods of the tree classes. It would be much cleaner to define each optimization separately, while still dispatching on the actual run-time type(s) of the tree nodes.

With Scheme, I could just download TinyClos and start using these features. They can be compiled efficiently too, though I’m not sure they are in Chicken. Knowing that it can be solved should the need arise is reassuring, though.

Further reading:

Scheme is not one Language

July 13, 2009

Considering all the hassle of using multiple languages in one project, was it really a good idea to use Scheme? I would say so.

Scheme is not like other languages. In a way, Scheme is not really one language, but a foundation for defining other languages. The semantics of Scheme were designed to enable programmers to devise their own control structures and the syntax was minimized to make it easy to parse, modify and layer your own syntax on top of. The language as a whole left so many other things unspecified that you must build your own environment on top of it unless you’re working on a small project.

One example of the power Scheme gives its users is unlimited continuations. A continuation is a point in the execution of a program which has been lifted up to the language layer, where it can be passed around or saved in variables, letting you execute it again later should you want to. That was pretty abstract so I’ll give you an example:

As you may know, functions in C programs push their return address on a stack before they are invoked. When a function returns, it pops the return address from the top of the stack and jumps to that address, which is where the call came from. The return address is the continuation of the caller (I’m simplifying here), but it doesn’t exist as a first-class entitiy in the C language, so we cannot see it or refer to it. In Scheme you can. Moreover, you can call a continuation as many times as you wish, and you can ask for the current continuation at any point, not just at call sites.

This is not terribly useful in day-to-day coding, but it is essential if you want to make your own language. Using continuations, you can implement exception-handling mechanisms like try-catch without resorting to low-level hackery (and things like loops with complicated exit logic and light-weight threads, too).

What all this comes down to is that Scheme is a very good environment for language experimentation, which has resulted in a lot of people hacking on these kinds of things. Consequently, you can go to the web page of your Scheme environment and download language extensions others have written that add or alter fundamental parts of the language; They may for instance add object-oriented constructs and run-time dispatch. Mainstream languages are much less malleable.

In summary, I think the ability to download language extensions and, so to speak, assemble my own language from pieces written by others has been a huge advantage. That, and the ability to make changes to the program as it’s running has been more than enough to justify the hassle.

Extended Scheme

July 13, 2009

I have now started to use the preprocessor on a small scale. All it does currently is translate dotted field expressions into the syntax TinyClos expects. I actually found out you could type  (slot@ foo x y) instead of (slot-ref (slot-ref foo ‘x) ‘y), but foo.x.y is better still, so I don’t regret writing the preprocessor. Besides, this way I can easily add more “macros” that don’t conform to Lisp’s grammar later.

This is not really a new language, just an extended variant of Scheme, but I still need a name for it to be able to talk about it.

Extended Scheme is too long a name, so I thought about shortening it to Extreme, but that doesn’t send the right message. Instead I’m calling it Steme (pronounced like steam), which you can interpret either as Ekstended Scheme or Stefan’s Scheme. If this ever grows into something serious I need to find another name for it.

Layout Language

February 8, 2009

To my surprise, I didn’t get any error messages any longer after I stopped using multithreading. All I had to do to fix it was catch all exceptions from the evaluation loop and call a function to print an error message when an exception was caught, but I didn’t realize that at first. Instead, I wasted many hours fighting with Chicken. I got tired and frustrated and didn’t get as much work done as I had hoped.

However, I did start hacking on a sublanguage for automatic layout of user interface components. The idea is that you should be able to specify a layout using a clean declarative language, much like HTML or Mozilla’s XUL. That way you don’t have to write as much code to arrange buttons and the like, and the specifications will be much more readable and easier to change than imperative code.

Here is an example of the kind of declaration I’m aiming to support:

(hbox (width: 100 %)
  (hbox (align: left) ship tiger stuka)
  (hbox (align: right) end-button exit-button))

A hbox is a container that arranges its components horizontally, from left to right. In this case, there is a hbox that contains two other hboxes. One of them is left aligned and contains the components ship, tiger and stuka; And the other one is right aligned and contains end-button and exit-button. In other words, it means something like the following pseudo-HTML:


<hbox width="100%">
 <hbox align="left"> ship tiger stuka </hbox>
 <hbox align="right"> end-button exit-button </hbox>
</hbox>

Box nesting does not work yet, but alignment does. Automatic layout management actually appears to be quite easy to implement, at least if all you want is something HTML-like. You just make two passes over the tree–first, a bottom-up pass that calculates the size of all components, and then a top-down sweep that positions them.

After writing some code for arranging components horizontally I asked a friend of mine who is very proficient in functional languages if he knew a general way of expressing the iteration that could replace the ad-hoc code I had written. He came up with this neat piece of code and its brilliant name:

;; Return a list of results from applications of ‘termfun’ to the
;; elements of ’sequence’ and a carry.
;; ‘carryfn’ is used to calculate the next carry from the previous one
;; and the current element of the sequence.

(define (map/carry termfn carryfn carry sequence)
  (if (null? sequence)
    ’()
    (cons (termfn (car sequence) carry)
      (map/carry termfn carryfn
        (carryfn (car sequence) carry)
        (cdr sequence)))))

Using map/carry I could express a function for horizontal distribution of components without using any explicit iteration. Beautiful! Thanks, Tord. I considered many names for a similar iteration construct that remembered earlier results, but none of them was as succinct as “map/carry”.

A New Breed of Programmer

November 30, 2008

Technological breakthroughs can enable new groups of people to compete successfully. For instance, the introduction of machinery in the industrial revolution did a lot to put women on equal footing with men, by reducing the advantage of physical strength. Something similar is happening in programming right now.

Patience, carefulness, and the ability to remember many facts and details were probably essential traits for successful programmers back in the early days of computers. Mistakes cost a lot of time when you don’t have a computer of your own and have to wait for the result until the next day.

After the arrival of the minis and personal computers, every programmer could have his own machine. This made a new style of programming possible, with rapid feedback and continuous experimentation as critical ingredients. Using Lisp you could even alter your programs as they were running. Getting everything right on the first try became less important under the new circumstances, and so the set of personality traits needed to be successful shifted.

The backlash against AI took Lisp and similar languages down with it, and in the mid-nineties almost everyone was using languages that had retained the edit-compile-link-run cycle. At the same time, code bases were growing larger and once again programmers found their workflows interrupted by long periods of waiting; This time not for access to a terminal, but for the compiler and linker to finish.

The arrival of Java and the Web turned the tide again–Java by moving Lisp concepts such as garbage collection back into the mainstream, and the Web by alleviating the need to memorize facts. Once again, the advantages meticulous and detail-oriented programmers had over their peers were reduced.

I think the circumstances have changed so much that the best programmers today are a whole different breed from the elite of the early days. The benefit of being able to keep lots of details in mind is much smaller now that you can find almost anything with Google in just a few seconds, and with new languages and tools that let you try many different approaches rapidly. In fact, I think some of the traits that once defined super programmers may even become liabilities. If you can find detailed information about anything instantly just by knowing the right keywords to search for, then trying to remember everything yourself is wasted effort. Just like a lossy compression algorithm can achieve higher compression rates than lossless ones, a less detail-oriented person has greater free capacity for learning abstract concepts than those who attempt to remember everything.

Exploring Troop Mobility Algorithms

September 14, 2008

Today I am very happy I made the effort to set up a development environment that makes explorative programming easy, and that I chose Scheme. It really pays off!

I have been experimenting with the algorithm for finding all hexagons that a unit can reach in one turn, and being able to see the effect of changes to the algorithm immediately–without restarting–makes it possible to work so much faster. You do not just get rid of the compilation time, you also do not have to set up your test case again after each restart, and having an uninterrupted work flow makes it easier to focus on the problem.

The purpose of the changes I made were to make it easier to block the opponent’s path with your troops. Until now, you could squeeze yourself in between enemy tanks, meaning the defender would have to have a completely air-tight front where every unit stood next to another to block your path.

The first screenshot illustrates the change. Previously, the blue tank was able to reach the hexes in the south by passing through the hexes between the enemy tanks. Now it takes half as many units to block the path, since the tank cannot pass through narrow passages that are bordered by enemies.

The second screenshot shows a situation where a tank has been surrounded by a circle of enemies which it cannot leave without a fight.

One of the reasons explorative programming was so useful here was that I was not sure exactly what I wanted when I started. I defined and redefined the problem as I was solving it. What counts as a narrow passage? Should you be able to step into hexes squeezed between enemies, but not pass through them? Should passage through hexes that are placed between the sea and an enemy be blocked too, or just hexes that are placed between two enemies? These questions would have taken much longer to answer had I used C++.

Forests and Phases

August 17, 2008

Last week I wrote about having different game phases. I realize now that “phase” was not the right word; What I meant was really “state”. The game does not have any phases in the right sense of the word. It has state, though–like every game–such as whose turn it is and whether the user is in the process of moving a unit. That part works pretty well now. Tanks can only be moved once per turn and only by their owners. The troops available for deployment can be restricted to arbitrary sets of troops and the interface is easy to use. You just click where you want to deploy a unit and if allowed it appears there and disappears from the troop deployment panel at the bottom. You can undo deployment decisions by clicking at the tank you want to move back into the undeployed set.

What’s missing now is the ability to end turns. There’s no button for it, so you have to evaluate a Scheme statement to end your turn =)

I have also been sketching on more types of terrain (forests and cities). I’m far from satisfied, but it’s a start. See the screenshot.

Other news is that Cairo is a bit slow and I have started thinking about learning OpenGL instead. The root causes are that Cairo’s rendering quality is too high (antialiasing is not necessary when zooming, for instance) and that it doesn’t use hardware acceleration.

The decision to use Scheme seems to have been an excellent one. With SWIG and Tinyclos I have complete control over the scene graph even from interpreted code, and I haven’t had any performance problems at all so far from Chicken.

Language Choice

July 31, 2008

Since I am doing this project in my spare time, the hours I put into it must be very productive, if anything is to come of it. That is why I will try to stay away from C++. I simply cannot afford to recompile and restart the game every time I make a tiny little change. The goal is to be able to code for whole days without restarting even once. On the other hand, I am sceptical about programming languages that pretend to be alone in the world and don’t let me use libraries written in other languages.

Add performance requirements and the number of languages I could choose from was pretty small. I considered Python, F#, and some other languages, but the one I settled for was Scheme.

Scheme has many advantages, the biggest one for me being that it can be used both for exploratory incremental programming and be compiled to C or machine code once you have a finished work. There are several mature and reasonably efficient compilers to choose from, and many of them let you interface with C easily.

I chose Chicken Scheme and am very happy with it so far. Right now, the game consists of about 600 lines of (mostly uncommented) Scheme code and 2500 lines of (very sparse and heavily commented) C++. The scene graph and the SVG parser are written in C++, and the rest will be written in Scheme.

Scheme has disadvantages too. It is essentially a write-only language, since it has close to no syntax at all. Standard Scheme also lacks pretty much everything when it comes to common data structures and algorithms, but with Chicken that seems to be less of a problem. Another thing I have come to like and to miss from Scheme is static typing. Catching errors at compile-time saves you so much trouble in the long run.

As the Scheme code grows larger I will probably have to do something about these things. I have experimented a bit with AntLR and it looks easy to write a “preprocessor” or compiler that translates from another syntax to Scheme. That way I could keep the advantages of Scheme while gradually adding static typing and a readable, python-like, syntax. But first of all I want to prove to myself that this project is going somewhere, so growing my own language will have to wait.