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.