The Insidious Eval Button (March 20, 2006)

Overview

In general, simplified theory is a powerful tool: it allows people to accomplish more with less mental effort. Sometimes, however, the simplification is overly strict, removing possibilities that are useful. This article focuses on an insidious assumption I think is overly restrictive: the assumption of an ever-present eval button.

The eval button is the step that converts a human-readable computer program into a computer-executable one. There is an inherent need to have some kind of divide between static program and dynamic execution. However, eval-button environments and theories make this divide absolute: there are programs, and there are executions, and the two do not mix. Likewise, if you change your mind and change the source program, then an eval-button environment makes you abandon any executions corresponding to the original version of the program.

The eval button shows up in a number of places:

  • In theory, it shows up in semantics that assume the program is fixed. If someone's language semantics is summarized by E(P) = v, then this "E" function is the eval button. Such theories quietly assume that the program does not change during execution; they make it impossible to even discuss such a thing.
  • In programming environments, the eval button shows up as the files+compilers arrangement and the corresponding edit-compile-run cycle. Such environments deeply assume that if you change the program, you should expect to abandon any running execution.
  • In programming language design, the eval button appears as heavy static semantics. The motto of the eval-button language designer is, "If it does not type check, it is not a program". In order to achieve a programming environment without the eval button assumption, it is likely necessary that the programming language be flexible enough to allow for slop.

In this article, I will take a look at the eval button for both programming environments and language theory.

No Reboots!

“No Reboots!” is the rallying cry. Eval-button environments insidiously presume that the running program corresponds exactly to the source code that spawned it. If the program changes, the executing process must necessarily be discarded. Stopping a program and then restarting it from scratch is nothing less than a reboot.

Example 1: Backwards Clock

I will now try, as best as possible on a web page, to show how no-reboot environments are useful to practitioners. The below examples are all done in Squeak. Demonstrating on a web page is difficult, because the interaction is the point. What I have tried here is to insert large numbers of screenshots so that the reader can hopefully picture the interaction. If it is not enough, you might also want to download David Buck’s video of a Smalltalk interaction.

The first example is to modify a graphical widget. I picked at random the “watch morph” widget which displays an analog clock. My first choice for a modification was to change the display to using roman numerals, but that modification has already been implemented! As a second choice, let’s make a clock that runs backwards.

The original watch morph looks like this. We start by making a duplicate of the watch morph and then telling it to be an instance of a new class. The new class is a subclass of WatchMorph that has no new methods. Thus, it begins by behaving exactly like the original. Now we can make some changes without affecting normal WatchMorph’s. (Incidentally, this kind of interaction is one of the most compelling reasons, to me, to have subclassing. If the modification is planned in advance, then delegation usually works better. It is for these after-the-fact ad-hoc modifications where subclassing is really helpful.)

To succeed in the modification, we first need to understand the original class. We can open a browser on the original class and look through its methods for what needs changing. If we look at drawOn:, the method for refreshing the drawn appearance of the morph whenever necessary, we can see three calls at the top to a method called radius:hourAngle:. These three calls are trying to decide where to draw the hour, minutes, and seconds hands of the watch. The method appears to convert a (radius,angle) pair into screen coordinates.

It appears that WatchMorph is factored so that watch-like (radius,angle) coordinates are consistently transformed via the radius:hourAngle: method. To quickly test this theory, we can open an inspector on our morph and try some sample calculations. These experiments are too fidgety to display very well on a web page; I will leave you with just one example–testing out radius 0.5 and hour angle 3 (for 3:00). The feel of this is just like jiggling around a physical apparatus to get a rough feel for how it works: the information is not conclusive, but you still like being able to do it.

This radius:hourAngle: method seems to be a key modification needed for BackwardsWatchMorph. We need to change it so that the angle is interpreted in a counter-clockwise direction instead of clockwise. We can browse to the original method in WatchMorph. This needs only slight modification for the purposes of BackwardsWatchMorph. I will not post an explanation, in case you want to try figuring it out yourself. In the modified version, I have highlighted the part that changes.

This modification actually turns out to be all that is necessary. WatchMorph is already well factored to consistently use this method in all the places that a time is mapped to screen coordinates! You simply have to resize the morph in order for the morph and the system to clear some caches, and the new morph is complete.

The final things the programmer should do are to try out the new BackwardsWatchMorph from scratch, and to browse through the list of changes we have performed along the way. It is possible that the sequence of incremental changes worked, but that some kind of bootstrapping issue was overlooked. In this case, there is no problem, and the code is finished as is, but it’s a good for good software engineering.

Example 2: Police Chase!

The previous example shows how a no-reboots environment can let the programmer leap seamlessly between code and object modification and browsing. Let us look at a more complicated example.

Police Chase! is a computer game where you race a police car down the highway trying to catch a bad guy. This example focuses on the police car itself. It is an interesting example because starting the game takes a minute or two while it loads textures.

Example 2a: Rate of Blinking

The police car has flashing lights on top of it. The code the causing the lights to flash on and off is straightforward. However, there is a question that is unanswerable at the command prompt: how fast should the lights blink? That is, what should the cycleTime variable be set to? This is an aesthetic question that can only be answered by trial and error.

In a no-reboot environment, it is no problem to adjust the rate as the program runs. Simply open an inspector on the car and adjust it until it looks correct. Once a good value is chosen, go back and change it in the original source code. Remember to eventually test from scratch again.

Instead of rebooting after trial flashing rate, the no-reboot environment allows programmers to delay rebooting until they are ready to test the bootstrapping code itself.

Example 2b: A Duration Class

The flashing rate is currently stored as an integer representing a number if milliseconds. Suppose we have decided that this number is used frequently enough that it is worth changing it to use an instance of class Duration to represent the time delay, instead of using an integer. That is, we want to change the type of variable cycleTime but not its semantics.

Code browsing tools can show all references to the variable. One or the other of these references must be changed first! Suppose we start by changing the definer. Instead of restarting the program, we can also change the value of the running instance using an inspector. Naturally, this last step causes a message-not-understood error. Parts of the code assume that cycleTime holds an integer, but now they see a Duration instead.

It is worth pausing a moment to consider the state of execution at this point. This is the state that eval-button proponents fear as if it were Armageddon come early. Let us take a deep breath, however, and observe the brokenness more camly—after all, broken code is part of development!

In this particular broken state, the lights of the car have stopped flashing, naturally enough. A debugger window has appeared offering a deeper look into the exact way that the code broke. Unrelated parts of the program still run, however! The road continues to flow by, as does traffic for the police car to dodge. The backwards watch morph created in the previous example continues to tick. The IDE itself is perfectly usable.

To continue onward, there is only one code patch that is necessary: the method pointed out by the debugger needs to have two calls to asMilliSeconds inserted. The system should then be told to resume executing the police car.

Missing Theory

As far as I know, no-reboot programmers operate right now in a theoretical vacuum. That is a pity. Theory should give a simplified and reliable foundation to our best practices. Let me sketch a few places where theory currently gets uncomfortable, where better theory could help us talk about and understand no-reboot environments:

  • What is the semantics? In general, most language semantics do not accommodate the program changing as it progresses. We should develop fundamental semantics that account for changes in the program, including changes that invalidate portions of the existing data.
  • Type checking? How should one think about type checking when the program might change after it is checked? What about the specific case where P checks, and P' checks, but P' is expected to keep executing with P's data still alive?
  • Defining program changes. How should program changes be defined? Ideally, they are not defined only as changes in the static program, but also in the dynamic execution state.
  • Refactoring and other nice changes. What does "refactoring" mean for changes that affect not only the program but also the execution state? Furthermore, what other well-behaved subsets of changes are worth defining?
  • Scope of effects. Intuitively, some changes to a program have effects that are limited. Changing an instance variable only affects code that accesses the variable. Is there any stronger notion of effect that is worth defining and supporting?

These are just a few ideas that sketch out this rich area of research.

Summary

I have described the “Insidious Eval Button”, the “no reboot” environments that avoid it, and possible theory directions to talk about no-reboot environments. Personally, I would love one day to address no-reboot environments in my own work. Meanwhile, the first step is to identify the issue and to recognize it is missing from our environments and our theory.