Professional Documents
Culture Documents
Coetzee Combining 2015
Coetzee Combining 2015
Coetzee Combining 2015
by
Supervisors:
Prof L. van Zijl
Dr M.R. Hoffmann
March 2015
Stellenbosch University https://scholar.sun.ac.za
Declaration
By submitting this thesis electronically, I declare that the entirety of the work
contained therein is my own, original work, that I am the sole author thereof
(save to the extent explicitly otherwise stated), that reproduction and pub-
lication thereof by Stellenbosch University will not infringe any third party
rights and that I have not previously in its entirety or in part submitted it for
obtaining any qualification.
i
Stellenbosch University https://scholar.sun.ac.za
Abstract
Interaction plays a key role in the process of learning, and a learner’s abilities
are enhanced when multiple cognitive functions work in parallel, especially
those related to language and visuals. Time is the most fundamental vari-
able that governs the interaction between programmer and computer, and the
substantial temporal separation of cause and effect leads to poor mental mod-
els. Furthermore, programmers do not have means by which to express their
mental models.
The feasibility of combining reverse debugging and live programming was
therefore investigated. This combination was found to be feasible, and a re-
verse debugger with higher levels of liveness was created for the Python pro-
gramming language. It establishes a foundation for combining language and
visual models as aids in computer programming education.
ii
Stellenbosch University https://scholar.sun.ac.za
Uittreksel
Interaksie speel ’n belangrike rol in die proses van leer, en ’n leerder se ver-
moëns verbeter wanneer verskeie kognitiewe funksies in parallel opereer, veral
dié wat verwant is aan taal en visuele denke. Tyd is die mees fundamentele
veranderlike wat die interaksie tussen programmeerder en rekenaar reguleer,
en die aansienlike temporele skeiding tussen oorsaak en gevolg lei tot swak
kognitiewe modelle. Programmeerders het boonop nie middelle om kognitiewe
modelle te artikuleer nie.
Die uitvoerbaarheid van ’n kombinasie van terug-in-tyd ontfouting en
lewendige programmering was daarom ondersoek. Daar was bevind dat so
’n kombinasie moontlik is, en ’n terug-in-tyd ontfouter met hoër vlakke van
lewendigheid was geskep vir die Python programmeringstaal. Dit vestig ’n
fondament om taal en visuele modelle te kombineer as hulpmiddels in reke-
naarprogrammering onderwys.
iii
Stellenbosch University https://scholar.sun.ac.za
Acknowledgements
I am indebted to:
• My Lord Jesus the Great Physician, for healing my back after a simple
prayer, when no one else could. And for inexpressibly more.
• The MIH Media Lab, for the financial assistance. As well as for believing
in me, and for the good coffee, the great people, and the spectacular
fußball.
iv
Stellenbosch University https://scholar.sun.ac.za
Contents
Declaration i
Abstract ii
Uittreksel iii
Acknowledgements iv
Contents v
List of Listings ix
List of Acronyms x
1 Introduction 1
Programming should be natural . . . . . . . . . . . . . . . . . . . . 1
Visual ways of thinking are indispensable . . . . . . . . . . . . . . . 1
Language and visuals must be combined . . . . . . . . . . . . . . . 2
Interactive, customisable tools are required . . . . . . . . . . . . . . 2
Interaction is governed by time . . . . . . . . . . . . . . . . . . . . 3
The objective of this study . . . . . . . . . . . . . . . . . . . . . . . 4
Thesis outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Background 6
2.1 Reverse debugging . . . . . . . . . . . . . . . . . . . . . . . . 8
2.1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.1.2 Omniscience . . . . . . . . . . . . . . . . . . . . . . . . 10
2.1.3 Reverse execution . . . . . . . . . . . . . . . . . . . . . 11
2.1.4 Benefits . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Live programming . . . . . . . . . . . . . . . . . . . . . . . . . 13
v
Stellenbosch University https://scholar.sun.ac.za
CONTENTS vi
3 Reverse debugging 20
3.1 Programming language . . . . . . . . . . . . . . . . . . . . . . 20
3.2 Reverse mechanism . . . . . . . . . . . . . . . . . . . . . . . . 21
3.3 Snapshots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.3.1 Using Python . . . . . . . . . . . . . . . . . . . . . . . 23
3.3.2 Using fork . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.4 Replay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.4.1 Through recording . . . . . . . . . . . . . . . . . . . . 27
3.4.2 Through snapshots . . . . . . . . . . . . . . . . . . . . 28
3.4.3 Intercept mechanism . . . . . . . . . . . . . . . . . . . 29
3.5 External state . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.6 Inter-process communication . . . . . . . . . . . . . . . . . . . 33
3.7 Back to the future . . . . . . . . . . . . . . . . . . . . . . . . 34
3.8 Control of execution . . . . . . . . . . . . . . . . . . . . . . . 36
3.8.1 bdb . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.8.2 pdb . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.8.3 epdb . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.9 The bidirectional ldb system . . . . . . . . . . . . . . . . . . . 42
3.9.1 Breakpoint management . . . . . . . . . . . . . . . . . 42
3.9.2 Intercept mechanism . . . . . . . . . . . . . . . . . . . 44
3.9.3 External state . . . . . . . . . . . . . . . . . . . . . . . 47
3.9.4 Inter-process communication . . . . . . . . . . . . . . . 48
3.9.5 Back to the future . . . . . . . . . . . . . . . . . . . . 51
3.9.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . 53
4 Live programming 55
4.1 User interaction . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.1.1 Awareness of change . . . . . . . . . . . . . . . . . . . 55
4.1.2 An IDE . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.2 Changes to consider . . . . . . . . . . . . . . . . . . . . . . . . 59
Stellenbosch University https://scholar.sun.ac.za
CONTENTS vii
List of References 87
Stellenbosch University https://scholar.sun.ac.za
List of Figures
viii
Stellenbosch University https://scholar.sun.ac.za
List of Listings
ix
Stellenbosch University https://scholar.sun.ac.za
List of Acronyms
IC Instruction Count
I/O Input/Output
x
Stellenbosch University https://scholar.sun.ac.za
Chapter 1
Introduction
1
Stellenbosch University https://scholar.sun.ac.za
CHAPTER 1. INTRODUCTION 2
found that many of the greatest thinkers, such as Albert Einstein, “avoid not
only the use of mental words, but also, just as I do, the mental use of algebraic
or any other precise signs . . . they use vague images” [44]. Richard Feynman
also, having once thought that “thinking is nothing but talking to yourself in-
side”, came to see that “thoughts can be visual as well as verbal” [39]. Visual
ways of thinking are indispensable.
CHAPTER 1. INTRODUCTION 3
Figure 1.1: “Reading the left-hand image requires the viewer to search the image
for the lowest and highest values, and the short-term memorization of the general
layout of the numbers. On the right, a qualitative understanding of the image is
immediately conveyed” [41, p. 34]
trol over the data on which a visual is based [46]. Programmers should be
able to express their own mental model to aid their thinking, and so make the
code habitable for them in the Christopher Alexander sense1 [42]. It suggests
the need for a visualisation framework, as part of the Integrated Development
Environment (IDE), to allow for the personalisation of any visual represen-
tations which the programmer would like to employ. “Ways of supporting
a programmer’s own system of imagery, integrated with the text of source
code” [54], should be pursued. People who are learning to program would find
it most valuable, as interaction plays a key role in the process of learning,
especially the learning of language [31, 68].
CHAPTER 1. INTRODUCTION 4
ger would allow them to do. Without a reverse debugger, the programmer has
to manually go through the entire stop-rerun-navigate cycle to simulate a single
backwards step, which introduces a substantial temporal gap. Furthermore,
the code is not connected to the running program so the programmer also
has to manually go through the stop-rerun-navigate cycle before code changes
have any effect. As programming consists of changing code, this is done regu-
larly. This “cause/effect chasm seriously undermines the programmer’s efforts
to construct a robust mental model” [36]. Incorporating visuals would not
furnish the programmer with more control of time, nor remove the temporal
chasm or reduce its negative impact, and would therefore not be of much value.
Programmers should consequently first be given control of time – from being
able to change the speed at which a program executes (and consequently the
speed at which connected visuals animate), to being able to reverse program
execution to go ‘back in time’, which a reverse debugger would allow. The
conversation between programmer and computer would become more lively,
which is also what ‘live programming’ aims to achieve by reducing the delay
between changing the code of an executing program and seeing the effects of
those changes. However “existing live programming experiences are still not
very useful” [53] and no system exists which allows for both reverse debugging
and live programming in a general programming language, indicating the need
for continued research in this area.
The objective The aim of this study is therefore to investigate the feasibility
of combining reverse debugging with live programming, to maximise control
of time and minimise the temporal separation of cause and effect. Such a
combination will establish a foundation for pursuing visual ways of thinking
in computer programming. If a reverse debugger with higher levels of liveness
should prove feasible, the goal will be to develop such a system. If, however,
such a system proves infeasible, the fundamental obstacles will be investigated
and explained, to aid future research.
Thesis outline Back-in-time debugging and live programming are each ex-
plained in more detail in chapter 2, with reference to other work that has been
done. The need for each, and the reasons for attempting to combine them,
is also discussed. The design and implementation of a reverse debugger is
Stellenbosch University https://scholar.sun.ac.za
CHAPTER 1. INTRODUCTION 5
Chapter 2
Background
Imagine how counter-productive it would be if, each time someone was asked to
repeat their last sentence, the entire conversation had to be repeated. Or worse,
if the entire conversation had to be repeated each time a different person was
about to speak. Unfortunately that is exactly what happens in most current
computer programming environments.
There is a difference between the manufacturing of an appliance in a fac-
tory, and the use thereof in a home. Similarly, it is important to differentiate
between the process of creating a computer program, and simply running a
completed program. A programmer, like an engineer or artist, has to work in
an environment which supports their needs while ‘manufacturing’ a program.
Programming is a type of conversation between the programmer and the com-
puter; a constant interaction with, and reaction to, each other [61, p. 1]. A
computer program executes forwards in time, but when a cause of an effect is
investigated in a running program, such as when tracing a bug, a programmer
has to search backwards in time. However, computers do not allow backwards
movement in execution time, which means that reversing has to be simulated
by the programmer, who has to restart the program and navigate to the point
just before – in other words, the entire conversation needs to be repeated each
time a backwards step is taken.
Furthermore, when a programmer ‘talks’ to the computer by changing the
code which produced a running program, the computer does not ‘talk’ back to
provide feedback about whether the change has had any effect – the running
program is separate from the code. In this situation too, the programmer
first has to repeat the conversation by manually going through the process of
stopping the running program, recompiling, restarting the program and recre-
ating the previous state, to see the results of the changes, if any. As this
entire process is regularly repeated, it is one of the most time-consuming parts
6
Stellenbosch University https://scholar.sun.ac.za
CHAPTER 2. BACKGROUND 7
of programming, and drives research in two directions looking for ways that
it might be alleviated. The first is for the programmer to be able to step
backwards in time while the program that they are currently working on exe-
cutes. The second is into higher levels of liveness during programming, where
the delay between changing code and seeing the result thereof, is minimised.
Although reverse debugging and live programming may at first appear to be
mutually exclusive, they are not – whereas reverse debugging is concerned with
issues before a possible code change, live programming is concerned with what
happens after that change. Both are concerned with reducing the temporal
separation between code and its effects.
Reducing the amount of time spent on programming would not be the
only or even greatest benefit of having a live system that can also reverse.
Interaction plays a key role in the process of language acquisition [31, 68].
Studies in the field of computer algorithm visualisation have found that greater
comprehension is attained when one has control over the data on which a
visual is based, and when one can interact with the visuals [46]. This suggests
the need for a customisable system in the IDE whereby a programmer can
construct their own visualisations that are connected to the instructions or
data of a program, to be able to ‘see’ program state and come to understand
an algorithm or piece of code. The output should change or animate as the
program executes. However, computer programs execute at tremendous speed.
Consequently the output would change too quickly for the programmer to grasp
the behaviour of the program. “What the programmer actually wants is to
watch the computation unfolding smoothly over time, changing slowly, gently,
predictably and meaningfully, and being presented in an appropriate visual
representation.” [64, p. 17]. The programmer should therefore be able to slow
down the output or connected visuals, and even pause or repeatedly reverse
and ‘replay’ it, to consume it at their own pace.
Using customisable visuals to observe the program state while manipulat-
ing it in this way, would result in a powerful environment in which to learn to
program. Two of Bruner’s [31] modes of representation – language-based sym-
bolic and image-based iconic – would be working in parallel. Interacting with
the visuals should also result in changes to code, covering Bruner’s final mode
as well, namely the interactive enactive mode. The programmer would be able
to create a custom VPL alongside the text. However, the visuals should not
Stellenbosch University https://scholar.sun.ac.za
CHAPTER 2. BACKGROUND 8
CHAPTER 2. BACKGROUND 9
operates correctly according to that idea. It often does not, and the reason for
it has to be found. This is called debugging, which makes up a large part of
programming.
2.1.1 Motivation
When a program is executed forwards in time, the data on which the program
state is based, and the process by which it changes, usually cannot be seen.
Some types of programs, such as virtual worlds or physical simulations, do
inherently display more of the state of the program. For these types of pro-
grams, changing code and re-executing, interacting with the program or even
just letting the program run, results in feedback which gives some degree of
insight into the code. However, not all parts of these programs are tied to some
form of output, and not all programs inherently visualise their state in that
way. Therefore, a programmer generally cannot tell when a program deviates
from its intended behaviour, and usually only detects that it has deviated at
some later point in time. The computer or programming environment does
not allow stepping backwards or viewing program state history, hence the pro-
grammer has to reason backwards from effect to cause. The process takes time
and is complicated, especially in larger programs and when the programmer is
inexperienced. Programmers often try to make computation visible by instru-
menting code with statements which print out or log the values of variables.
This form of debugging is called ‘print and peruse’ or ‘dump and diff’ [36]. If
the instrumentation code has been placed correctly, the programmer hopes to
be able to use the output the next time the program is run, to observe where
it deviates from the intended behaviour. The code usually has to be changed
and the program rerun for each step backwards in the causal chain anyway, as
it is rare to go from an incorrect effect to its cause in a single step. The cyclic
nature of this form of debugging is evident in that it requires the program
to be stopped and re-executed after each instrumentation. This is not ideal,
due to the extra time it takes to reproduce state, and because state cannot
always be reproduced, which results in “large temporal or spatial chasms be-
tween the root cause and the symptom” [36]. Furthermore, the programmer
has to remember to remove the instrumentation code as it is not actually part
of the program, but merely exists to serve the programmer while developing
the application.
Stellenbosch University https://scholar.sun.ac.za
CHAPTER 2. BACKGROUND 10
2.1.2 Omniscience
The computer, having forward-executed the program up to some point, should
really be able to provide us with information about it. Such an improvement
is possible, and is found in what are called omniscient debuggers [62]. They
are usually classified as reverse debuggers, although they might more accu-
rately be described as “history logging” debuggers [30, p. 299], as they merely
record information during execution to view or query later, rather than allow
the programmer to actually step backwards in time in an executing program.
“Omniscient” comes from the fact that the entire state history of the program,
having been recorded, is available to the debugger after execution. There is
then no need to rerun the program, and no need for manual code instrumen-
tation.
Software-based omniscient debugging started with the 1969 EXDAMS sys-
tem where it was called “debug-time history-playback” [29]. The GNU debug-
ger, GDB, has supported omniscient debugging since 2009, with its ‘process
record and replay’ feature [7]. TotalView [5], UndoDB [18] and Chronon [9] ap-
pear to be the best omniscient debuggers currently available, but are commer-
cial systems. TOD, for Java, appears to be the best open-source alternative,
which makes use of partial deterministic replay, as well as partial trace cap-
turing and a distributed database to enable the recording of the large volumes
of information involved [62].
According to a 2013 study by the Judge Business School at Cambridge
University, programmers spend 26% less time on debugging when using omni-
Stellenbosch University https://scholar.sun.ac.za
CHAPTER 2. BACKGROUND 11
scient debugging tools, which would translate into global savings of $41 billion
annually, should all programmers use them [24]. However, as the overhead of
recording and querying the entire state history of a program is usually pro-
hibitively high, omniscient debuggers are still not commonly found in modern
IDEs [62, p. 15].
CHAPTER 2. BACKGROUND 12
CHAPTER 2. BACKGROUND 13
2.1.4 Benefits
There are a number of benefits in using a reverse debugger rather than an om-
niscient debugger, specifically for beginner programmers. The first is that a
reverse debugger is less costly in terms of both hardware and runtime overhead,
as only nondeterministic events need to be managed, not the entire trace his-
tory. Although more expensive hardware might be a suitable requirement for
professional programmers, an inexpensive workstation should be sufficient for
a student who wants to learn to program. The second benefit of working with
a step-wise rather than omniscient debugger is that one sees the computation
unfolding when navigating either forwards or backwards in time, which gives
a student a better grasp on exactly how the computer executes code. Some
omniscient debuggers are more capable of directly revealing causes [62, p. 7],
which, though a useful tool for a professional programmer, does not provide the
learning programmer with a solid foundation in procedural thinking. Another
benefit to reverse debugging is that the program continues to execute, which
means that it is able to continue reacting to different inputs. This is useful to
both professional programmers testing their code, and to those learning how
to write programs that are stable in a wide variety of situations.
The ability to move backwards in a live program is also where live pro-
gramming comes into play – when reverse execution is used to trace a bug, the
ideal system would allow the programmer to change the code of the executing
program and have the program immediately reflect that change, so that the
program may continue executing as before.
CHAPTER 2. BACKGROUND 14
CHAPTER 2. BACKGROUND 15
CHAPTER 2. BACKGROUND 16
CHAPTER 2. BACKGROUND 17
CHAPTER 2. BACKGROUND 18
at all times.
2.2.4 Approach
Having clarified that change should be propagated in such a way that the
program state always reflects the state of the code, the question should again
be asked, how can a change to code be propagated in a running program
so as to minimise the amount of time spent waiting for the program to re-
flect it? It is a difficult problem to solve in a general programming language.
There are a number of ways to approach it, such as simply re-executing the
program and deterministically replaying previous inputs, designing the pro-
gramming language to manage state as a first-class entity, or some form of
dependency analysis for change propagation. The exploration of these ideas
has generally focused on design at the language level, rather than only on
the environment [52, 53]. Edwards states that “The fundamental reason IDEs
have dead-ended is that they are constrained by the syntax and semantics of
our programming languages” [10]. Alan Kay, the man behind concepts like the
Dynabook, Smalltalk, OOP and more [47], thinks “that a large part of pro-
gramming language design . . . is treating the language and how it is worked
with as user interface design” (emphasis in original) [48, p. 3]. Though I
believe that much progress will be made through the design of new program-
ming language models, as the “design of the language is just as critical to the
programmer’s way of thinking as the design of the environment” [16], design-
ing new models is no mean task, especially models that focus on time-travel
and change propagation. However, as live programming is “emerging as the
next big step in programming environments” [53, p. 9] and “is an idea whose
time has come” [32, p. 10], there have been a number of experiments. No-
table attempts, most of which have focused on design at the language level,
include ALVIS LIVE! [45], SuperGlue [52], LPX [53], Acar’s Self-Adjusting
Computation [25] and more recently Circa [40], Moon [50] and some of Vic-
tor’s ideas [15, 16]. However, McDirmid [53, p. 9] recently concluded that
“existing live programming experiences are still not very useful”, indicating
the continuing need for research in this area. Instead of focusing on the design
of new programming languages, I chose to investigate how live programming
might be brought to an existing programming language.
Stellenbosch University https://scholar.sun.ac.za
CHAPTER 2. BACKGROUND 19
2.3 Conclusion
The only combination of reverse-step debugging and live programming, ap-
pears to be the 2006 ALVIS LIVE! development environment, which has seen
promising results with novice programmers [45]. It brings together direct ma-
nipulation of visuals, immediate feedback, and reverse execution with control
of speed. Unfortunately, it uses a simple, pseudocode-like language, as in-
dividual commmands are undone behind-the-scenes through the execution of
another command, similar to how a pop might be used to undo a previous
push to a stack. The language has been further restricted to support only
single-procedure algorithms that involve array iteration.
The combination of reverse debugging and live programming, to form a
foundation on which to pursue the integration of language-based and image-
based ways of thinking for computer programming education, appears to be
promising. There exists a need for such a combination in a general program-
ming language, and its feasibility will be investigated in this study.
Stellenbosch University https://scholar.sun.ac.za
Chapter 3
Reverse debugging
20
Stellenbosch University https://scholar.sun.ac.za
3.3 Snapshots
There are different ways in which snapshots could possibly be made and acti-
vated, to facilitate reversing.
Stellenbosch University https://scholar.sun.ac.za
from the start. However, the interpreter does not provide access to all the
necessary state information in these dictionaries anyway. This is due to the
fact that some of the built-in functions, as well as a number of modules in
the PSL, make use of the underlying operating system. One such case is
Input/Output (I/O) functionality, where Python makes use of the buffering
mechanism of the operating system, which means that the interpreter does
not have access to the file buffers, which it needs to be able to save their state
when making a snapshot. Trying to implement snapshots in the Python virtual
machine of the CPython implementation, or by writing an extension to the
Python interpreter, does not circumvent these problems. Complete snapshots
can therefore only be made at the level of the operating system.
Figure 3.2: Pycallgraph uses the f_trace attribute of a stack frame for call graph
visualisations
3.4 Replay
Snapshot-and-replay serves to simulate reversing by making use of an earlier
snapshot, combined with automatic, behind-the-scenes, replay-like forward-
execution to reach the intended destination. To have it appear to the user
as if a simple backwards step was taken when reversing, the intermediate
instructions have to be replayed in such a way that the program reaches the
same state as before.
Instructions that depend only on the internal state of the program, are
called deterministic instructions. An example is an instruction that adds two
numbers. Given the same program state as before, deterministic instructions
automatically execute in exactly the same way each time, so they do not pose
a problem during replay. A nondeterministic instruction, however, depends on
the state of the environment, so usually results in different behaviour when
re-executed.
For example, consider the nondeterministic seed() function in the random
pseudo-random number generator module of the PSL. A call to random.seed(),
as in Listing 3.1, sets the seed of the generator by using a randomness source
provided by the operating system – usually the current system time. The seed
determines the sequence of numbers that the generator then deterministically
generates.
1 random.seed()
2 x = random.random()
3 print(x) # 0.912413526333249
When the user reverses at a later point in the program, but the pro-
Stellenbosch University https://scholar.sun.ac.za
gram continues from a snapshot before these instructions, they are re-executed
behind-the-scenes. The seed is consequently set to a different value because
the system time has changed, and the generator then returns different values
than it did before. The user would consequently notice that the program had
done more than simply reverse a step, as instructed.
A mechanism is therefore required to manage nondeterministic instructions,
so that returning to a previous point in time would also return the user program
to the same state as before.
There are a number of different ways in which nondeterministic functions
can be managed. When the debugger compiles the user program to an Abstract
Syntax Tree (AST) or code object, it could make changes to that object before
executing it. Parts of the user program could then be replaced with code
that behaves differently, so that behaviour can be reproduced on subsequent
executions. As everything in Python is a first-class object, Python also allows
for the runtime replacement of objects, and allows for the module loading
mechanism to be customised as well. Objects could thereby be replaced only
if and as necessary, which would be faster.
1 def seed () :
2 if mode == 'normal':
3 original_random.seed()
4 store['random.seed'][IC] = original_random.getstate()
5 elif mode == 'replay' :
6 original_random.setstate(store['random.seed'][IC])
1 def seed () :
2 original_random.seed()
3 debugger.make_snapshot = True
When the user chooses to reverse, the snapshot from which to replay would
then be directly after the closest previous nondeterministic instruction, as can
be seen in Figure 3.4. The snapshot to activate might even appear at a later
Stellenbosch University https://scholar.sun.ac.za
point (though still before the intended destination), if snapshots are also made
to place an upper bound on replay time. As all instructions to be replayed
are purely deterministic, the problem of recording and correctly replaying the
behaviour of nondeterministic instructions then falls away.
More snapshots are made when using this technique. This is generally not
a problem, as forking is a lightweight operation, and large programs need not
be catered for in the educational assistance scenario. It does become a problem
when a great number of snapshots are made, leading to a type of fork bomb. To
manage the number of snapshots, different strategies could be combined with
the snapshot strategy, such as exponential checkpoint thinning [30, p. 306],
or the recording strategy explained in section 3.4.1. Recording could also
prove to be useful when program state needs to be serialized, or when visually
animating the execution of a section of code which includes a nondeterministic
instruction. Future work might look at the best ways for these two techniques
to work in parallel.
1 import random
2 print(random.seed.__doc__) # None
In epdb, contrary to the explanation given by its author, “if a patch module
provides a symbol, which does not exist in the original module” [63, p. 37], it
will not be ignored, and is accessible by the program at <module>.<symbol>.
The reason is that the replacement mechanism works by executing the replace-
ment module in the namespace of the original module. This overwrites any
symbols already found in the symbol table of the original module, which is
how the replacement is made possible, but it also enters any symbols not yet
found there. Although generally not a serious issue, care has to be taken in
the replacement modules, so that none of the symbols of the original mod-
ule are unintentionally replaced, leading to unexpected behaviour in the user
program.
Another consequence of the epdb mechanism is a name conflict problem,
which the Boothe bdb debugger also suffered from [30, p. 300]. The epdb
mechanism replaces the module for the entire system, so that there is no longer
a distinction between the module which the debugger uses, and the module
which the user program uses. This leads to problems, as the augmented be-
haviour of the module is only meant for the user program, not the debugger.
The debugger has to make use of many of the functions that need to be re-
placed, such as some built-in functions like open, and modules like time, from
the PSL. When the the user program calls the functions of the time module,
for instance, it has to trigger the creation of a snapshot by the debugger to
manage the nondeterministic behaviour, as explained in section 3.4.2. How-
ever, when the debugger uses the time functions, which it does to keep track
of how much time has elapsed since the previous snapshot was made, it should
not trigger the creation of more snapshots. Epdb tries to circumvent the prob-
lem by checking the name of the file from which a call originates, and avoids
making the snapshot if the call appears to come from an epdb file. This black-
list approach results in unexpected behaviour when a user file has the same
name as an epdb file, and when extending the epdb system with more files.
As epdb was meant to be extensible, especially the replacement mechanism,
this approach is not ideal.
Stellenbosch University https://scholar.sun.ac.za
The ldb replacement mechanism does not suffer from the name conflict
problem. It also addresses the similarity of replacement objects to the originals,
which is discussed in more detail in section 3.9.2.
minimise the overhead of the system, ldb does not store resource data on
the server, and restores resources at a different time, which is discussed in
section 3.9.3.
3.8.1 bdb
Bdb is the base debugger framework class in the PSL. The way it provides
a debugging framework is by registering itself with the interpreter as a trace
hook, so that control passes to it before certain tracing events during the
execution of the program. Bdb contains a number of abstract functions for
implementation by derived classes. Based on the trace event, the derived class
can then manage what should happen at that point, using its implementation
of the abstract functions. If it returns control to the interpreter, the instruction
is executed. The next trace event then takes place and the cycle is repeated.
The way in which bdb registers itself as a trace hook, is through the
settrace() function of the PSL sys module. This sets the f_trace attribute
of the stack frame so that a bdb function is called before every trace event in
that frame, as mentioned in section 3.3. The explanation given for each trace
event type in the documentation for the sys module5 , is as follows:
line The interpreter is about to execute a new line of code
or re-execute the condition of a loop.
5
Available at http://docs.python.org/3.2/library/sys.html
Stellenbosch University https://scholar.sun.ac.za
3.8.2 pdb
Pdb, another PSL class, extends the bdb class and implements the abstract
functions. By also extending the cmd module of the PSL, which is a frame-
work for line-oriented command interpreters, pdb enables user interaction by
Stellenbosch University https://scholar.sun.ac.za
providing a prompt when a stop condition is met, in the terminal wherein the
debugger was started. It makes the standard debugging commands, like step,
next and continue, available to the user. By providing a mechanism for the
evaluation of arbitrary Python code in the context of the top stack frame at
any point, it allows for the exploration of specific program behaviour without
changing program code, which is discussed in more detail in section 3.9.5.
3.8.3 epdb
Epdb is built on pdb and therefore bdb, but implements extra functionality
for reverse debugging. It provides a number of other commands, some of which
have the form r<command>, as they are the reverse counterparts of forward-
execution commands. For example, rstep is the counterpart of step.
Bookkeeping
For normal forward-execution, epdb uses the bdb stop conditions. However, a
number of changes have to be made to implement reverse debugging.
To allow for reversing to a specific position in the program, a debugger has
to count instructions, so that the position can be identified [56]. However, bdb
only moves forward during execution, so does not have to keep track of the IC.
Consequently, bdb is optimised to defer to the user_<event> functions only if
it has to stop. To implement reverse functionality, the bdb method therefore
has to be adapted. In epdb, the trace hook function of bdb is changed so
that it does not check for stop conditions, but instead passes control to the
extended debugger via the user_<event> functions, which manages the stop
condition checks instead [63, pp. 18–19].
As a result of the change to the bdb method, the call stack height has to
be recorded for use by the next command. The height is measured in frames,
so the value of the counter that keeps track of it, is called the frame count,
whereas it was called the call depth counter in the Boothe bdb debugger [30,
p. 300]. The counter is incremented on call events and decremented on return
events. During normal forward-execution, the frame count is noted when the
next command is given, and the debugger stops again on the next line event,
after an instruction is reached with either the same or a lesser frame count.
For reversing, there are other conditions that cause epdb to stop for user
Stellenbosch University https://scholar.sun.ac.za
interaction as well, such as when the start of the program is reached. The stop
conditions for the rnext and rcontinue commands warrant special consider-
ation.
For the stop condition of the rnext command, a stack is used in the form
of a python list. The current IC is pushed to the list on every call event. On
every return event, the corresponding IC is again popped from the list, and is
entered into an rnext_dict dictionary with the current IC + 1 used as the key.
In this way, corresponding IC pairs are recorded, which describe a call and
the IC that the call returns to, as illustrated in Figure 3.6. This information
is used when an rnext command has been given, to determine the destination
IC, which is consequently used to determine the closest previous snapshot to
activate for replay.
Snapshot activation
There are three ways in which a snapshot can be activated in epdb, described
by three activation types, namely counting activation, frame count activation
and continue activation [63, p. 42].
Stellenbosch University https://scholar.sun.ac.za
Continue activation works in the same way, except that the destination IC
depends on the next breakpoint. The closest previous snapshot to the desti-
nation IC, or the latest snapshot in the timeline if there is none, is activated,
and directed to continue.
Stellenbosch University https://scholar.sun.ac.za
only local to the process. In ldb, breakpoints would also be lost when the main
debugger process is ended and a new one started, which might need to happen
as part of the live programming mechanism. This means that a server for
breakpoints is required, but is not sufficient unless it is persistent. Interaction
with breakpoints also has to be fast, as breakpoints are used at every IC as part
of the stop condition checks. The breakpoint management of epdb is therefore
improved in ldb, to increase the speed of the system, by keeping both a local
and server copy of the breakpoint dictionaries. When a breakpoint is created or
changed, both the local and server copies are affected, but only the local copy
is otherwise referred to. When a snapshot is activated, the local breakpoint
manager receives an update from the server, before execution continues. This
allows for both speed and persistent breakpoints even when reversing to an
earlier point in time. The breakpoint management found in bdb has therefore
been changed and moved to the BreakpointManager class, which manages
Breakpoint instances. The breakpoint functions of bdb have been adapted
to interact with the new breakpoint management class. The server in ldb is
simply a separate process, created not through forking of the main debugger
process as in epdb as none of the debugger functionality is required by the
server, but as an independent process by using the multiprocessing package
of the PSL. Communication between the server process and the main debugger
process uses the same socket communication class as the rest of the ldb system,
as detailed in section 3.9.4.
Ldb requires a Graphical User Interface (GUI) for implementing higher
levels of liveness, which is discussed in section 4.1.2. It might appear possible
to treat the GUI process as the server, as it is already persistent, and break-
points will be manipulated through the GUI. However, when a snapshot is
activated and it creates a new main process, the process has to request the
most recent breakpoint dictionaries from the server before it continues, which
requires an immediate response. The GUI might send other commands to the
main debugger process in the interval between the activation of the snapshot
by the current main process, and the new main process continuing to consume
the commands from the GUI. This means that a race condition is introduced
– when the new main process requests the breakpoint data from the server,
other commands might have already been sent by the GUI, so that the data
that the main process receives is not the breakpoint data, but a command.
Stellenbosch University https://scholar.sun.ac.za
This could be circumvented by using an extra socket only for breakpoint com-
munication, but to allow for persistent breakpoints even on the command line
when the GUI is not used, a dedicated process for the server is a better design.
1 first = [1]
2 second = first
3 id (first) # 30464768
4 id (second) # 30464768
5 second.append(5)
6 print (first) # [1, 5]
Having more than one name that is bound to the same object, has to be
avoided, to prevent modifications to the original objects by the replacement
mechanism. Fortunately, it is also possible for a single name to refer to different
objects, if that name appears in two different namespaces, as in Listing 3.6.
1 var = 1
2 def func () :
3 var = 5
4 print (var) # 5
5 func()
6 print (var) # 1
a name in that namespace can refer to a different object than the original,
creating an isolated sandbox in which the user program is executed. This
is possible, as the exec function which is used by the debugger to execute
the user code, can be provided with global and local dictionaries which form
the namespace in which the user code is run. By using the same names,
but changing what those names refer to in the namespace of the user code,
different behaviour can be given to the user code without the original objects
being changed.
A naive approach would be to replace the references directly, with replace-
ment objects that are defined in the debugger. However, an object has access
to the surrounding scope of the place where it is defined, and the replacements
are defined in the debugger instead of the sandbox in which the user program
is executed. The __globals__ attribute of an object, which is a dictionary
that defines its global namespace, cannot simply be replaced, as it is read-
only. The items in the dictionary can be manipulated, but to remove all the
references to debugger objects in the surrounding scope, would again require
a blacklist approach. The references would have to be updated whenever the
debugger code is changed.
To resolve the scope problem, replacement objects that are based on the
original objects could be created, by using the standard types defined by the
interpreter, such as FunctionType for functions. A different dictionary can
then be provided, for the replacement objects to use as their global namespace.
A simpler method is to define each replacement object in a limited scope, such
as within a module containing no initialisation code. That module could then
return the replacement, which is possible, as everything in Python is a first-
class object.
The replacement objects would not automatically mimic the object they
are replacing – attributes like the docstring would be different. To fix this for
functions, a decorator can be used, such as the wraps decorator, provided by
the functools module of the PSL. The attributes of the function will then
be identical to those of the original function, as far as possible.
Modules that the user program imports, have to be replaced as well. For
the debugger to accurately reflect the way that the user program would execute
without the debugger, the replacement must only happen when a module is
imported, not when the debugger is initialising. This can be accomplished
Stellenbosch University https://scholar.sun.ac.za
sources. When a snapshot is activated, such as when the user reverses, the file
offset will not necessarily be what it was when the snapshot was created, but
will be what it was after the other process had manipulated the file. When
the resource manager writes to the file to restore it to its previous state, the
content that has been written to the file since the creation of the snapshot, is
not necessarily overwritten. This has been fixed in ldb.
Ldb does not use a controller process as in epdb, and so requires a different
mechanism for all the processes to be able to communicate, if necessary. The
way that the communication is set up, is by creating a pair of connected
sockets. After forking, the parent process is the snapshot and the child process
is the main process. Although it is a duplex connection, the snapshot blocks
while listening for messages using one of the sockets, so it will be called the
listening socket end. The child process, using the socket at the other end of the
connection, only initialises communication with the snapshot when it needs to,
so it will be called the sending socket end. As the connection has to be created
before forking, each process initially has both sides of the connection, but then
closes the side they do not use, which Figure 3.8 illustrates.
Stellenbosch University https://scholar.sun.ac.za
Each socket gets wrapped in a higher-level Snapshot class, which acts as the
communication contract between snapshots. The Snapshotting class manages
all snapshot-related functionality. It keeps track of Snapshot instances in its
snapshots dictionary, indexed by the IC that each snapshot is blocking at.
When a snapshot is about to be created, a new Snapshot instance which
contains the sending socket is placed in the snapshots dictionary at the current
IC. The child process can then message the snapshot process using that entry.
A later call to activate the snapshot made at instruction 4 might then look as
follows: snapshots[4].activate(). This calls the activate method of the
Snapshot instance at index 4 of the snapshots dictionary, which sends the
“activate” message, using the sending socket. The snapshot process which
was made earlier when the program was at instruction 4, is always listening
on the listening socket, so immediately receives the message.
Each time a process forks, the child is a copy of the process, so still has
the snapshots dictionary containing all the sending sockets connected to the
receiving sockets of previous snapshots. In this way, each process can commu-
nicate with every earlier process. This is illustrated in Figure 3.9.
Snapshot activation
Having no redo mode in ldb means that, when a past snapshot is activated
when a user reverses, there is no more need for the existing future snapshots,
and they should be instructed to shut down, to free up resources. However,
having no controller in ldb means that a past snapshot has no knowledge of
Stellenbosch University https://scholar.sun.ac.za
future snapshots, and therefore cannot tell them to shut down. This means
that as part of the process of activating an earlier snapshot, the main process
has to manage the shutting down of all processes which come after that earlier
snapshot, including itself. It sends a shut down message to all intermediate
snapshots, and after sending an activate message to the snapshot, it uses the
exit system call to shut itself down as well. When the earlier snapshot then
activates, it forks to create a new process to take over as the main process.
This procedure is illustrated in Figure 3.10.
one direct child. It also guards against orphan processes that would be created
were the parent to continue as the main process, but later shut down when
an earlier child snapshot process is activated. Figure 3.11 is a more accurate
portrayal of snapshot creation in ldb.
Before any snapshot either shuts down or activates, it waits for its child
process to shut down first, by using the wait system call. In Figure 3.11, if
process p1 is activated, it waits for c1 to shut down before it reaps it and con-
tinues, but c1 in turn waits for c2 to exit before it will shut down. When c2
does exit, the exiting of processes cascades up, which ensures that no descen-
dant processes exist before p1 will continue, to guard against the formation of
defunct or zombie processes.
Figure 3.11: When forking, each parent process blocks as the snapshot, and its
child continues
Process p1 will then create a new child to take over as the main process,
and again start blocking, waiting for commands from any descendants. This
is illustrated in Figure 3.12.
Figure 3.12: The state of processes after the p1 process of Figure 3.11 has been
activated
move forward again, having no redo mode simply means that the user, after
having reversed past a nondeterministic instruction, might not automatically
step forward in the exact same way. It will only be an issue if the user wants
exactly the same behaviour as before. This will not often be the case, so
should not be a problem in practice, especially in programming education.
Changing code is what programming consists of, and a program is regularly re-
executed as part of the development cycle anyway. So if deterministic replay of
nondeterministic instructions does become important to the learner for some
unforeseen reason, the behaviour of that specific instruction could be hard-
coded while the user explores it. The speed by which that can be done and
again undone, will also be much reduced by having a live system.
There is also a way to explore a specific behaviour of a nondeterministic
instruction without changing the program code, should the programmer want
to avoid the hard-coding of specific values as part of the development process.
Commands can be given to the interpreter at any point where the execution of
a program has been paused, through the prompt that the debugger provides.
This functionality comes from pdb, as mentioned in section 3.8.2. It allows the
programmer to change the program state after the nondeterministic instruction
has executed. The user could simply run a command which affects the program
state in such a way that it is as if the nondeterministic instruction displayed
a specific behaviour. For example, consider a program that uses the time()
function in the time module of the PSL, to return the operating system time,
before printing it:
1 x = time.time()
2 print(x)
The user might want to test the behaviour of the print() function when
the time() function specifically returns a value of 1234567890, without hard-
coding that value into the program code. To do this, the program can be
paused directly after the x = time.time() nondeterministic instruction has
executed, and the value of x can then be replaced in the running program
by typing !x = 1234567890 at the debugger prompt. When the user then
steps forward, the print instruction is executed with that specific value for x,
without a change to the program code.
Stellenbosch University https://scholar.sun.ac.za
3.9.6 Conclusion
The reverse debugging functionality of ldb, whose design and implementation
has been discussed in this chapter, was informed by the PSL bdb and pdb
classes, the Boothe bdb debugger [30] and the epdb prototype [63]. Epdb
could not be extended directly, due to the state of the code base – parts
were no longer used, and the complex interactions between the proxy objects
in different processes, resulted in unexpected behaviour that could not be
resolved. Epdb also contained functionality which ldb did not require, namely
its redo mode and timelines, with accompanying activation modes. Therefore,
ldb was built from the ground up, using bdb, pdb, the Boothe bdb debugger
and especially epdb as reference. A number of changes were made, not only
to fix defects like orphan and zombie processes and the name conflict problem
of the intercept mechanism in epdb, but to reduce the complexity of the code
base, and to allow for future visualisation, which is where this project is headed.
The speed of the system was also improved in a number of ways, to facilitate
the integration of live programming features while still reducing the temporal
gap as much as possible.
An example of what interaction with ldb is like when using the command
line interface, is shown in Figure 3.13, where the program given in Listing 3.9
is being debugged.
Figure 3.13: The command line interface of ldb when tracing Listing 3.9
Stellenbosch University https://scholar.sun.ac.za
Chapter 4
Live programming
Higher levels of liveness are about changes to code, and minimising the amount
of time that passes before the user receives feedback about the effect of the
change, as explained in section 2.2. How might the reverse debugger bring
higher levels of liveness to a program that is under its control?
55
Stellenbosch University https://scholar.sun.ac.za
receive feedback about their changes as they edit the code, not only when they
perform some action to request feedback.
Higher levels of liveness further imply that the debugger should not even
wait until the user has finished editing a specific symbol, before calculating the
effects. For example, the intention of the user might be to change the name of
a variable from var to long_var, and the user does this by prepending each
character of the string long_ in turn. As the user is typing, it is impossible to
know what the intention of the user is – the user might have the name lovar
in mind instead. Only registering the change after a short interval of inactivity
might save computational overhead, by preventing many intermediate changes
from being propagated. However a fast typing speed of 240 characters per
minute would still mean that the interval has to be longer than 0.25 seconds to
prevent change propagation while the user is editing a single, multi-character
symbol. That could already be too long an interval for the user to wait at
the end of the edit, before the system even begins to propagate the change.
Novice programmers are also likely to have a slower typing speed, not only as
a result of having spent less time using a keyboard, but because they have to
deliberately reason about each change, more than an experienced programmer
would. Therefore, the better strategy seems to be to propagate all changes
immediately, which requires that the debugger be made aware of every single
change. Future usability studies are required to evaluate the best strategy
for balancing the speed of feedback with the computational overhead – each
user should perhaps be able to customise the behaviour of the system, in this
regard.
4.1.2 An IDE
For the editor, or the IDE in which the editor is found, to notify the debugger of
every change to code, it requires that the editor either saves the file on every
code change, or that it notifies the debugger directly. Generally, editors do
not save a file on every change, and do not have an event handler registration
mechanism whereby another program can be notified of events. It is also an
infeasible task to provide an extension for all editors or IDEs which users might
want to use, so it was decided to implement a basic IDE for ldb. The focus
of ldb is on education, and novice programmers have not spent much time in
any IDEs, so requiring the use of a different editor is not of great concern.
Stellenbosch University https://scholar.sun.ac.za
Responsiveness
It is not possible to tell whether a statement in the user program is still be-
ing executed, or whether the program is hanging. For example, a blocking
receive call on a socket might be faulty, or might just be waiting for data.
The user interaction functionality should not become unresponsive as a result,
which would happen if the same process was providing the user interaction
functionality and executing the user program. This happens in pdb, as the
pdb class extends the bdb.Bdb class to provide debugging functionality, but
also extends the cmd.Cmd class to provide user interaction functionality. A
process to interact with the user, which is separate from the process in which
the user code is executed, is a better design for a system that allows for the
execution of user code written in a general programming language. In ldb, the
GUI is therefore run as a separate process.
Most interactive applications have a main loop which checks for user in-
teraction events, and Tcl/Tk is no different. Event handler functions can be
1
Available at http://www.tcl.tk
Stellenbosch University https://scholar.sun.ac.za
registered, which the main loop will dispatch to for specified events, so that
the GUI can react to those events. This allows for a GUI to be created that
sends specific commands to the debugger when certain actions, such as the
click of a button, are performed.
A communication mechanism is consequently required between the debug-
ger and the GUI. An unnamed socket pair is created in the GUI, and when
the GUI initialises the debugger as a completely separate process by using
the Process function of the multiprocessing package, the listening socket is
passed to the debugger. The socket wrapper class that is used for communica-
tion between the main debugger process and the snapshot processes is reused,
to manage the encoding and decoding of data. The debugger, instead of wait-
ing for the cmd module to provide commands, then waits for data from the
GUI. Further improvements to this architecture are discussed in section 5.4.1.
Execution of the user code by the debugger might take any amount of
time. The GUI therefore should not block for a response from the debugger
after giving it an execution command, as it would again result in an unre-
sponsive program, and so nullify any gain from separating the GUI from the
debugger process. In other words, an asynchronous communication mechanism
is required. The debugger needs to be able to initiate communication with the
GUI at the appropriate time, to provide information about the execution of
the user program, for the GUI to process and display when it is ready to do
so. To this end, a callback function is registered for the Tcl/Tk main loop to
call periodically. The main loop carries on processing user interaction events
until the specified amount of time has elapsed, which allows the user to stop
the debugger even if it becomes unresponsive. The callback function checks
the socket for any communication from the debugger, using a non-blocking
mechanism. If there is information from the debugger, such as the line that
the debugger has stopped at, it is processed before control returns to the main
program loop. As all processing of information from the debugger results in
little computational overhead, the GUI handles the processing itself. However,
if it does become more computationally expensive in future as the ldb system
is expanded, the GUI should create a new handler process and delegate the
processing to it, so as not to hold up the main program loop. If data needs
to be sent to the debugger in response, the socket could be passed to the new
process so that it can respond to the debugger directly. The callback function
Stellenbosch University https://scholar.sun.ac.za
finally registers itself to be called again, after another interval of the specified
amount of time has elapsed.
Listing 4.1: A change can affect the program both directly and indirectly
When the code is edited, all three these areas should be updated for their
state to be a reflection of the execution of the new program, and no longer
the execution of the previous program. A complete analysis on every edit, to
determine all the ways that a change could affect those areas, is not the way to
approach the problem for a general, dynamic programming language – some
information is available for static program analysis, but much information is
only available at runtime. For example, consider the user program in List-
ing 4.2. To what degree would the rest of the program be affected by a change
to the variable a, on line 1? It is impossible to tell, as it depends entirely on
the input that the user provides on line 2, at runtime.
1 a = 5
2 exec(input())
3 x = 2 * a
A different execution run from the start, even for the same program, might
lead to a different execution path being followed as a result of nondetermin-
istic instructions. The point where the user was previously at, might there-
fore not be reached again. For example, in Listing 4.3, the program might
not return to line 2, 3 or 5 if that were the POE. If the POE were line 6,
the program would be able to return to it, with the value of x possibly be-
ing different than before. It can also be seen that the IC would be different
at line 6, depending on which branch of the conditional statement had exe-
cuted, which means that the POE cannot be defined in terms of the IC. For
now, it is instead defined by the line that the program was previously at.
Stellenbosch University https://scholar.sun.ac.za
1 if 5 <= random.randrange(10) :
2 x = 3
3 y = 0
4 else :
5 x = 7
6 print(x)
Nondeterminism
Listing 4.4: Nondeterministic instructions should not be replayed after the point
of change
by chance. For example, consider the program in Listing 4.5. The square
function should return the square of a given number, but instead of returning
the result of the calculation, the function has a bug which causes it to always
return the number 4. The program uses the random module to nondetermin-
istically provide a number as input, which provides the number 2 during the
first execution, by chance. The assert on line 5 does not raise an error, even
though the square function is faulty. If, in this way, only the first behaviour
shown by each nondeterministic instruction is ever used, a program would
more often appear to be correct when it is not, as deterministic behaviour is
not the true nature of a nondeterministic instruction. However, if the program
were to be regularly ‘massaged’ through the re-execution of nondeterminis-
tic instructions, bugs would surface more often, leading to better programs.
Novice programmers would not yet make use of other means – such as unit tests
as part of a build process – of exposing their programs to multiple executions.
The re-execution of nondeterministic instructions is therefore to be preferred
over replay. Just as in the traditional development cycle, the user should ex-
plicitly hard-code specific behaviours if they want to retain them, otherwise
make use of the mechanism discussed in section 3.9.5, so that nondeterministic
instructions do not, by default, behave deterministically.
The question of how to return to the same POE has therefore not yet been
answered.
Execution history
A compromise might seem like a better solution, where the previous POE is
stopped at only if it is reached, but where the program continues to execute if
it is not reached. The POE might therefore be controlled by the user through a
breakpoint. However, as the user is able to freely navigate through the program
execution without setting breakpoints, and as breakpoints might already exist
Stellenbosch University https://scholar.sun.ac.za
1 def save(val) :
2 print('Same POE?')
3
4 if 0.5 < random.random() :
5 x = fp.write('data')
6 save(3)
7 else :
8 save(7)
Listing 4.6: The same line number can be reached in different ways
Stellenbosch University https://scholar.sun.ac.za
point. The user could then decide how to deliberately move forward from
there, by continuing to a breakpoint after a function call, for instance. As the
user works with lines of code in the editor, the change point could simply be
defined by the specific line number in the source code where the change has
taken place. However, even the change point proves difficult to capture and
return to.
It may be argued that the new program only has to be executed up to
the change point, if that line was reached in the previous execution of the old
program. If that were true, the line number to IC mapping that is recorded by
the debugger during forward-execution for use by the rcontinue command,
as discussed in section 3.8.3, could be used to determine whether or not a line
had been reached. If the line had not been reached, the new program would
not have to be executed yet. If the line had been reached, it might have been
reached multiple times, so the debugger would have to use the first time that
the line was reached, as the stopping point for the new execution. However,
the change point is not necessarily the first place where the two programs
diverge, depending on the presence of nondeterministic instructions, which are
likely to display different behaviours when the new program executes. It means
that the change point could trigger the execution of the new program, and yet
not be reached again. The point to stop at therefore becomes a subjective
matter again, unless all nondeterministic instructions up to the change point
are replayed. It is not as problematic to replay the behaviour of only these
nondeterministic instructions, as they have not been affected by the change in
the code, which only comes later. Although it does not avoid the broken clock
scenario described in section 4.3.2 for the nondeterministic instructions up to
the change point, constantly replaying their first behaviour would be the only
way of ensuring that the change point is definitely reached again.
First consider the simpler situation where the change point is the POE, or is
in the future relative to the POE. In Python, the debugger cannot simply be
directed to continue tracing different code. A workaround that seems like it
could work, without yet considering how to navigate back to the POE in the
new program, is to do an exec of the new program, so that it becomes the
program that is traced, but even this cannot be done directly. The debugger
functionality can be divided into two parts – an inside and an outside, so to
speak, which refer to their use, relative to the user program. The outside part
is used before and after the execution of the user program, and contains func-
tionality for setting up and tearing down both the debugger and the sandbox
in which the user program will execute. As part of setting up, the debugger
also puts a callback trace function in place, through sys.settrace. When
the debugger executes the user program by using exec, the trace function is
called for every trace event. The trace function calls a number of debugger
functions, which can be grouped with the other parts of the debugger that
they make use of, as the inside part, due to their use during the execution
of the user program. These are the functions that stop for user interaction,
which means that the only place for execution of the user program to stop,
is inside the trace function. The problem with executing the new program in
that situation, is that the new program does not trigger trace events, which
the debugger requires. A minimal example which demonstrates this, is given
in Listing 4.7. When this program is run, it can be seen that only the outside
exec triggers trace events.
Listing 4.7: Code executed within a trace function, does not trigger trace events
the new exec call while inside the old, but as the old tracing state is saved to
be restored after the new exec returns, it continues to use resources. As a new
exec would be done each time the code was changed, it would quickly lead to
all available resources being consumed.
To exec a new program without unnecessarily holding on to resources, the
current exec first has to be broken out of. This can be done by raising and
catching a particular exception. The debugger then does an exec of the new
program in the old context, which was retained through a handle on the context
dictionaries provided to the initial exec. The new exec takes the debugger
back to the first line, but it can immediately return to the POE before any
instructions are executed, by writing to the f_lineno field of the stack frame,
as explained in section 3.3.1. In this way, the call stack and internal and
external states are retained, so the instructions before the POE do not have to
be re-executed, and nondeterministic behaviours do not have to be replayed.
It leads to a significant improvement in speed, proportional to the execution
time of the instructions before the POE. For example, the sleep call on line
1 of Listing 4.8, which could represent 10 seconds of computational overhead,
is not executed again when a change is made to line 3 when the POE is line
2, unlike when the new program is executed from the start.
1 time.sleep(10)
2 print('the POE')
3 print('this is changed')
The situation where the change point is in the past, relative to the POE,
can now be considered. The debugger has to return to the earlier change
point, which the reverse capabilities of ldb allow it to do. The closest previous
snapshot to the change point is activated. It breaks out of the exec, and its own
position is jumped back to immediately after the exec of the new program.
The snapshot then has to forward-execute to reach the change point. The
reverse debugging strategy of making a snapshot after every nondeterministic
instruction, means that the instructions that remain to be executed between
the positions of the snapshot and the change point, are guaranteed to be
Stellenbosch University https://scholar.sun.ac.za
deterministic, meaning that the change point will definitely be reached again,
as shown in Figure 4.2.
Figure 4.2: Returning to a change point that is before the current POE
Complications
Although this strategy allows the debugger to quickly return to the change
point, without replay and without much re-execution, it cannot always do so.
Having to write to the f_lineno field, limits the snapshot positions that can be
directly jumped back to. Some compound statements such as for loops, while
loops, try suites and except clauses, require special initialisation and clean-up
of information on entry and/or exit, such as the value of an iterator or point to
return to when leaving the statement. This is already taken care of in ldb by
activating and retaining the state of a snapshot, which is a program that has
already done the required initialisation. For example, even though a finally
clause, after being directly jumped into it in pdb, causes a segmentation fault
when it is exited, it does not do so in ldb. Python, however, never allows most
blocks to be jumped into directly, and cannot be told of the ldb workaround
so that it would know that it is safe to allow the jump. The use of f_lineno
would therefore be only slightly less problematic here than in section 3.3.1, so
either only snapshots in the top-level namespace have to be activated, or the
snapshot positions are not the points that should be jumped back to. If only
top-level snapshots can be activated, the closest previous snapshot to a position
is not necessarily the one to activate, which would mean that nondeterministic
instructions are no longer taken care of automatically, and that re-execution
Stellenbosch University https://scholar.sun.ac.za
1 def func() :
2 print('this is changed')
3 a = 1
4 b = 2
5 func()
Listing 4.9: The point where a code block is defined, has to be returned to
As the parent has undergone a change, its parent has to be updated in turn,
all the way back to the top-level namespace. It would have spared much
execution if only the lines where the entities are defined, could be re-executed
to register each change in turn, and the change point be jumped to immediately
thereafter, but it is not possible either due to the limitations of writing to
f_lineno – a code block cannot be jumped into. The point to return to
therefore has to be the closest previous point to where the outermost parent
entity is defined, in the top-level namespace.
How can the IDE tell to which entity a line belongs, so that the correct line
can be returned to? Having first tried, unsuccessfully, to record the information
during execution, ldb now makes use of the AST, which already has to be
constructed, as explained in section 4.2.1. For each entity, the lines on which
it is defined and ends, are captured before execution of the new program, by
using the AST. When a line is changed, this information is used to check
whether the line is part of an entity. If it is, the line where the outermost
parent entity is defined, is used as the line to return to instead. Listing 4.10
Stellenbosch University https://scholar.sun.ac.za
shows an example of the line, given as a comment, that a change on that line
would return the debugger to.
1 a = 1 # 1
2 def func () : # 2
3 x = 1 # 2
4 class InnerClass : # 2
5 def innerClassFunc (self) : # 2
6 z = 1 # 2
7 func() # 7
Listing 4.10: The place to return to when lines are changed, is where the outermost
parent entity is defined in the top-level namespace
The debugger can use the same approach for the other compound state-
ments, such as for and try. The line where the statement is defined, becomes
the destination line for the debugger to return to, when a line is changed that
belongs to the statement. The snapshot to activate when reversing, would be
the closest previous snapshot to the first time that the destination line was ex-
ecuted. However, the forward jump is to the snapshot position, as was shown
in Figure 4.2. As the snapshot position cannot necessarily be jumped back to,
and as the destination line is guaranteed to be a point that can be jumped to
in the top-level namespace, the jump should only occur once the destination
has been reached, not when the snapshot is activated. This allows the closest
previous snapshot to be used, so that nondeterministic instructions are auto-
matically taken care of and the least amount of re-execution takes place, while
the debugger still traces the new program. Figure 4.3 illustrates this strategy.
1. activate snapshot
As the point that the debugger returns to is different to both the POE
and the change point, it makes it even more important that the position in
the source code where the execution is at, at any moment, has to be clearly
indicated to the user, which is done by highlighting the relevant line in the
GUI, as was shown in Figure 4.1.
Stellenbosch University https://scholar.sun.ac.za
Chapter 5
5.1 Overview
5.1.1 Motivation
This study was motivated by the desire to place humans, instead of computers,
at the centre of HCI. As a human interacts most naturally when using lan-
guage and visual faculties in parallel, these should be utilised simultaneously
during programming. However, existing programming interfaces have a strong
bias towards either language-based or visual-based interaction – no satisfac-
tory combination exists. The need to maximise interaction and customisability
when combining visuals and text, suggests the need for a visualisation frame-
work to enable programmers to express their own mental models, as well as to
receive feedback from the computer by way of that same channel. Such a sys-
tem would be most valuable to novice programmers, as interaction and clear
communication are essential to the learning process. All interaction is how-
ever fundamentally governed by time. The considerable temporal separation
of cause and effect during programming, hinders the formation of robust men-
tal models. This separation would still exist when visuals are incorporated. A
solid foundation for integrating visuals consequently first requires the minimi-
sation of delays in time, as well as the maximisation of the control of time. To
this end, this study investigated the feasibility of combining reverse debugging
and live programming in a general programming language, which had not been
done before.
74
Stellenbosch University https://scholar.sun.ac.za
cute up to the point, called the POE, where the previous program execution
was stopped. The way to define the POE was considered. Nondeterministic
instructions which result in different execution paths when the program is ex-
ecuted from the start, were explained to be a major obstacle when trying to
return to that point. Deterministic replay of all nondeterministic instructions
up to the POE was contemplated, but it was demonstrated that nondetermin-
istic instructions could not be replayed after the point where the old and new
programs diverge. An explanation of why constant replay would be a disad-
vantage, and why re-execution is to be preferred, was also provided. Defining
the stopping point in a way similar to how breakpoints work, was consequently
examined. It was argued that it would likely lead to confusion, especially for
novice programmers. Usability studies were suggested to ascertain the amount
of confusion that this would cause, to determine whether the point to return
to should be defined in this way. Usability studies were also suggested to de-
termine whether novice programmers might expect the program to return to
specific positions, and if so, what those points would be in different situations.
Consideration was given to returning the program to the change point
instead. The deterministic replay of all nondeterministic behaviour up to the
change point was again found to be necessary to be able to reach the change
point. As the old and new program would therefore be identical up to that
point, a significant improvement could be considered – not re-executing that
part of the program at all. It was indicated that this strategy would depend on
being able to direct the debugger to trace the new program. As Python does
not allow this directly, workarounds were investigated. Simply doing an exec
of the new program was shown not to be possible, and it was explained that
the call_tracing workaround would quickly consume all available resources.
A strategy which broke out of the current exec but which retained the state
of the program, did an exec of the new program, and jumped back to the
previous position before any execution took place, was suggested. For future
change points, relative to the POE, it appeared to hold much promise, in that
it avoids re-execution of all instructions before the change point, so allows for
a direct return to the POE. For change points in the past, relative to the
POE, the reverse debugging functionality was ideal for allowing the program
to first return to the change point before continuing with the strategy, and
then to deterministically execute up to the change point. The use of f_lineno
Stellenbosch University https://scholar.sun.ac.za
to jump back to the same position after the new exec, limits this strategy
however. Although ldb already takes care of the reasons for the interpreter
disallowing a jump at certain times, there is no way to inform the interpreter of
that, so a workaround has to be implemented. In the meantime, the strategy of
only jumping to allowed positions in the top-level namespace was investigated.
Lines which change object definitions were considered, and the consequent need
to return to the line where the outermost object definition was registered in
the top-level namespace. The restrictions on f_lineno again prevented only
the definition lines from being re-executed before jumping back to the previous
point. Different methods were investigated for identifying the definition lines
in the top-level namespace, and using the AST was found to be the optimal
approach. The break-exec-jump strategy was also slightly altered to allow
for the continued use of the previous snapshot, to automatically take care of
nondeterministic instructions.
5.1.5 Summary
A reverse debugger with higher levels of liveness was investigated and imple-
mented by using the general, multi-paradigm programming language Python,
which is widely used for computer programming education. Improvements
to previous reverse debugger approaches were discussed and implemented, es-
pecially to facilitate the integration of live programming. The need for a
reverse debugger to manage side effects was also explained and addressed.
The approach used for navigation through time allows for future visualisation
functionality.
The fundamental, language-independent concerns of live programming were
discussed and addressed. Change propagation was investigated, and the need
to consider its effects on the internal program state, the external environment
and the call stack was explained. The optimal way to propagate changes in
a way that covers these areas, by using the least amount of execution, was
pursued. The approach of executing the entire new program was shown to be
feasible, yet non-optimal. The concepts surrounding the POE were explained,
as well as the difficulties that would be encountered when returning the new
program to such a point. An optimal strategy was proposed of returning to
the change point by making use of the reverse debugging functionality, and
only executing from there to the POE. It avoids all unnecessary execution.
Stellenbosch University https://scholar.sun.ac.za
5.3 Shortcomings
Although the ldb system was created and the fundamental difficulties ad-
dressed, there remains work to be done.
Stellenbosch University https://scholar.sun.ac.za
The first issue that has not been overcome, is the determination of the optimal
stopping point for the execution of the new program. The presence of any
nondeterministic behaviour after the change point would mean that the point
to return to cannot be captured or described in a way that allows for use across
different runs. Capturing that point by its line number, as with breakpoints,
might lead to confusion – only a usability study would be able to determine if
it would. The optimal points to stop at in different situations could perhaps be
determined subjectively, which a usability study would again throw light on.
As usability studies have not been done as part of this study, the optimal point
at which to stop the execution of the new program, has not been determined.
Consequently, ldb currently returns the user to the change point instead, for
the user to manually navigate from there, which is not ideal.
The second issue that is not adequately addressed in the current state of the
ldb system, is the inability in Python to instruct the debugger to continue
tracing a different program from any specific point onwards. Although the
reverse debugging functionality proved ideal for returning to the change point,
the debugger cannot be instructed to continue executing the new program
from the change point on. The ldb workaround of breaking out of the current
exec but retaining the program state, doing an exec of the new program,
and immediately jumping back to the previous position, is limited by the
restrictions that the Python interpreter places on f_lineno. Ldb therefore
currently returns the program to an earlier point in the top-level namespace
that can be jumped to, which is not ideal.
There is another consequence to the interpreter’s f_lineno restrictions for
which no workaround has yet been implemented. Even though snapshots al-
Stellenbosch University https://scholar.sun.ac.za
low navigation backwards in time, not necessarily being able to jump forwards
to their positions for live programming, also has an impact on their use by
the reverse debugger. Although the jump limitation could be circumvented in
section 4.3.4 by first executing to a position in the top-level namespace, which
becomes the position that is jumped to, the same cannot happen when a snap-
shot is activated when reversing. Once a new program is being traced by the
debugger, after following the procedure that is illustrated in Figure 4.3, new
snapshots that are created also trace the new program, but activating an ear-
lier snapshot reverts the debugger to the program that was being traced when
the snapshot was made. The snapshot would have to break out of the exec,
exec the new program, and jump back to its own position before continuing.
However, the same problem is encountered as earlier, where the snapshot posi-
tion cannot necessarily be jumped back to due to the limitations of f_lineno.
This is an unsolved problem. The only solution, so far, seems to be to activate
the closest previous snapshot that is at a position that can be jumped to, and
execute to the intended destination from there. This would have an impact
both on re-execution time, and on nondeterministic instructions, which are
automatically handled by the snapshot strategy, as explained in section 3.4.2.
The recording strategy, introduced in section 3.4.1, might prove useful here,
though not ideal. If a different way, that does not make use of a jump, cannot
be found of instructing the debugger to continue tracing a different program
from a specific point, it would be necessary to patch the Python interpreter.
As the Python programming language is already used widely in computer
programming education, I had hoped that patching the interpreter would not
be necessary, so that this system would be easier to roll out, and consequently
lead to faster adoption. However, patching of the interpreter appears to be
necessary, to minimise the amount of re-execution that needs to take place,
and so truly minimise the temporal separation between code changes and see-
ing its effects. The number of changes to make to the interpreter would be
minimal. The only change would be to remove the f_lineno jump restrictions
for registration of the new program with the debugger, as ldb already takes
care of the reasons for these restrictions, through forward execution from an
earlier snapshot. It would remove the issues with continuing execution of the
new program from a change point that is inside compound statements, and
the issues with not being able to jump to all snapshots that are still tracing
Stellenbosch University https://scholar.sun.ac.za
the old program, so that they can also continue with execution of the new
program.
Once the best position to stop execution at can be determined through
a usability study, and once the debugger either allows tracing of a different
program from any point or no longer restricts jump positions for the optimal
ldb workaround to be implemented, the states of the program and the envi-
ronment will be able to fully reflect the new code with the minimal amount of
execution.
5.4.1 Architecture
As the main debugger process currently manages the execution of the user
program as well as the setting up and tearing down of the surrounding frame-
work and the breakpoint server, when the user program hangs and is stopped,
the framework and server are stopped as well. It would be better to divide
the parts of the debugger, so that the inside and outside parts, described in
section 4.3.4, are separate processes, which would communicate over UDS as
well. The outside part would do the setting up, tearing down, interaction with
the GUI and management of the different processes. The inside part would
manage only the execution of the user program, and would then be the main
process as defined in section 3.3.2, from which snapshots are made. It would
execute and report back to the outside part, then block, waiting for instruc-
tions. The outside part would block for interaction from the GUI, unless the
main process is busy executing, when it would not block but periodically check
for commands from the GUI as well as check on the status of the main process.
Should the user then reverse in the GUI when the user program seems to be
hanging, the command can be sent to the outside process, which would simply
terminate the main process and activate the previous snapshot. In this way,
Stellenbosch University https://scholar.sun.ac.za
even when the user program is unresponsive, reversing would still be possible,
which is functionality that might often be required by novice programmers.
only the __code__ attribute and not the __closure__ attribute is changed.
As the __closure__ attribute is read-only, more work would need to be done
to determine whether the scope of the new entity can be made to be identical
to the previous scope. Tracing of the new entity also appears to be affected.
5.5 Summary
This study was introduced in chapter 1, and the motivation behind it was
expanded in chapter 2, where reverse debugging and live programming were
introduced. The investigation of how they might be combined, was explained
to be the main goal of this study. Consideration was given to the approaches
of other reverse debugging and live programming systems, and some neces-
Stellenbosch University https://scholar.sun.ac.za
sary clarifications were provided. It was found that, although they could work
well together, no combination of reverse debugging and live programming for
a general programming language had been attempted before. The design and
implementation of a robust reverse debugger, informed by the top reverse de-
bugging approaches so far, was detailed in chapter 3. It was constructed in
such a way that it allowed for higher levels of liveness and future visualisation.
The strategies for bringing higher levels of liveness to the reverse debugger
were discussed in chapter 4, and the implementation of the optimal strat-
egy resulted in the live reverse debugger, ldb. The results of the study were
discussed in chapter 5. It showed how the mechanisms used for the reverse de-
bugger also proved useful for implementing the live programming features, and
that the objectives were achieved. Improvements and future possibilities were
also explored. Although work remains to be done, ldb already forms a solid
foundation on which to combine visuals with language, for use in computer
programming education.
Stellenbosch University https://scholar.sun.ac.za
List of References
87
Stellenbosch University https://scholar.sun.ac.za
LIST OF REFERENCES 88
[11] (2012). CS101 – Intro to computer science: Build a search engine & a social
network. Available at: http://www.udacity.com/course/cs101. Retrieved:
20 February 2015. 21
[17] (2012). The Standford education experiment could change higher learning
forever. Available at: http://www.wired.com/2012/03/ff_aiclass/all/.
Retrieved: 20 February 2015. 21
LIST OF REFERENCES 89
[22] (2013). Hacking meets clubbing with the ‘algorave’. Available at:
http://www.wired.co.uk/magazine/archive/2013/09/play/algorave.
Retrieved: 20 February 2015. 15
[25] Acar, U.A. (2009). Self-adjusting computation. In: Proceedings of the 2009
ACM SIGPLAN Workshop on Partial Evaluation and Program Manipulation
(PEPM), pp. 1–6. ACM Press, New York, New York, USA. Available at:
http:
//www.umut-acar.org/publications/pepm2009.pdf?attredirects=0. 18
[27] Archer, Jr., J.E., Conway, R. and Schneider, F.B. (1981). User recovery and
reversal in interactive systems. Tech. Rep., Cornell University, Ithaca, New
York, USA. Available at: http:
//ecommons.library.cornell.edu/bitstream/1813/6316/1/81-476.pdf.
11, 22
[28] Balchin, W.G.V. and Coleman, A.M. (1966). Graphicacy should be the fourth
ace in the pack. Cartographica: The International Journal for Geographic
Information and Geovisualization, vol. 3, no. 1, pp. 23–28. Available at:
http://utpjournals.metapress.com/openurl.asp?genre=article&id=
doi:10.3138/C7Q0-MM01-6161-7315. 1
LIST OF REFERENCES 90
[31] Bruner, J.S. (1964). The course of cognitive growth. American Psychologist,
vol. 19, no. 1, pp. 1–15. Available at:
http://bienser.umanizales.edu.co/contenidos/lic_ingles/
desarrollo_cognitivo/criterios_conceptuales/recursos_estudio/pdf/
The%20course%20of%20cognitive%20growth_Bruner.pdf. 1, 3, 7
[32] Burckhardt, S., Fahndrich, M., de Halleux, P., Kato, J., McDirmid, S.,
Moskal, M. and Tillmann, N. (2013). It’s alive! Continuous feedback in UI
programming. In: Proceedings of the 34th ACM SIGPLAN Conference on
Programming Language Design and Implementation (PLDI), pp. 95–104.
ACM Press, New York, New York, USA. Available at:
http://research.microsoft.com/pubs/189242/pldi097-burckhardt.pdf.
8, 13, 18
[33] Collins, N., McLean, A., Rohrhuber, J. and Ward, A. (2003). Live coding in
laptop performance. Organised Sound, vol. 8, no. 03, pp. 321–330. Available
at: http://yaxu.org/writing/laptop_performance.pdf. 15
LIST OF REFERENCES 91
[38] Feldman, S.I. and Brown, C.B. (1988). Igor: A system for program debugging
via reversible execution. In: Proceedings of the 1988 ACM SIGPLAN and
SIGOPS Workshop on Parallel and Distributed Debugging (PADD), pp.
112–123. ACM Press, New York, New York, USA. Available at:
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.85.
2238&rep=rep1&type=pdf. 11, 22
[39] Feynman, R.P. (1988). “What do you care what other people think?”: Further
adventures of a curious character. WW Norton & Company. 2
[42] Gabriel, R.P. (1996). Patterns of software: Tales from the software
community. Oxford University Press, New York, New York, USA. Available
at: http://www.dreamsongs.com/Files/PatternsOfSoftware.pdf. 3
[45] Hundhausen, C.D. and Brown, J.L. (2007). What you see is what you code: A
“live” algorithm development and visualization environment for novice
learners. Journal of Visual Languages & Computing (JVLC), vol. 18, no. 1,
pp. 22–47. Available at: http://citeseerx.ist.psu.edu/viewdoc/
download?doi=10.1.1.107.7938&rep=rep1&type=pdf. 18, 19
Stellenbosch University https://scholar.sun.ac.za
LIST OF REFERENCES 92
[46] Hundhausen, C.D., Douglas, S.A. and Stasko, J.T. (2002). A meta-study of
algorithm visualization effectiveness. Journal of Visual Languages &
Computing (JVLC), vol. 13, no. 3, pp. 259–290. Available at:
http://www.cc.gatech.edu/~stasko/papers/jvlc02.pdf. 3, 7
[47] Kay, A. (1993). The early history of Smalltalk. In: Proceedings of the 2nd
ACM SIGPLAN Conference on History of Programming Languages (HOPL
II), pp. 69–95. ACM Press, New York, New York, USA. Available at:
http://worrydream.com/EarlyHistoryOfSmalltalk/. 18
[48] Kay, A., Ingalls, D., Ohshima, Y., Piumarta, I. and Raab, A. (2006).
Proposal to NSF: Steps toward the reinvention of programming (granted on
August 31, 2006). Available at:
http://www.vpri.org/pdf/rn2006002_nsfprop.pdf. 1, 18
[49] Lam, L.C. (2001). A survey of data breakpoint and reverse execution. Tech.
Rep., Experimental Computer Systems Lab, Stony Brook University, Stony
Brook, New York, New York. Available at:
http://www.ecsl.cs.sunysb.edu/tr/rpe12.ps.gz. 22
[50] Lemma, R. and Lanza, M. (2013). Co-evolution as the key for live
programming. In: Proceedings of the 1st International Workshop on LIVE
Programming (LIVE), pp. 9–10. IEEE Press, San Francisco, California, USA.
Available at: http://liveprogramming.github.io/2013/papers/moon.pdf.
18
[53] McDirmid, S. (2013). Usable live programming. Proceedings of the 2013 ACM
SIGPLAN Conference on New Ideas in Programming and Reflections on
Software (Onward!). Available at:
Stellenbosch University https://scholar.sun.ac.za
LIST OF REFERENCES 93
http://research.microsoft.com/pubs/189802/onward011-mcdirmid.pdf.
4, 18
[55] McLean, A., Griffiths, D., Collins, N. and Wiggins, G. (2010). Visualisation of
live code. In: Proceedings of the 2010 International Conference on Electronic
Visualisation and the Arts (EVA London), pp. 26–30. British Computer
Society, London, England. Available at:
http://yaxu.org/writing/visualisation-of-live-code.pdf. 8
[57] Norman, D.A. (1993). Things that make us smart: Defending human
attributes in the age of the machine. Addison-Wesley, New York, New York,
USA. 2
[58] Oney, S., Myers, B.A. and Brandt, J. (2013). Euclase: A live development
environment with constraints and FSMs. In: Proceedings of the 1st
International Workshop on LIVE Programming (LIVE), pp. 15–18. IEEE
Press, San Francisco, California, USA. Available at: http://www.
joelbrandt.org/publications/oney_live2013workshop_euclase.pdf. 14
[59] Paivio, A. (1971). Imagery and verbal processes. Holt, Rinehart and Winston,
New York, New York, USA. 1
[60] Pan, D.Z. and Linton, M.A. (1988). Supporting reverse execution of parallel
programs. In: Proceedings of the 1988 ACM SIGPLAN and SIGOPS
Workshop on Parallel and Distributed Debugging (PADD), pp. 124–129. ACM
Press, New York, New York, USA. Available at:
http://doi.acm.org/10.1145/68210.69227. 11, 22, 25
Stellenbosch University https://scholar.sun.ac.za
LIST OF REFERENCES 94
[66] Tanimoto, S.L. (1990). VIVA: A visual language for image processing.
Journal of Visual Languages & Computing (JVLC), vol. 1, no. 2, pp. 127–139.
Available at:
http://linkinghub.elsevier.com/retrieve/pii/S1045926X05800126. 14
[67] Tolmach, A. and Appel, A.W. (1995). A debugger for Standard ML. Journal
of Functional Programming, vol. 5, pp. 155–200. Available at:
http://web.cecs.pdx.edu/~apt/jfp95.ps. 12, 22
[69] Wilcox, E., Atwood, J.W., Burnett, M.M., Cadiz, J.J. and Cook, C.R. (1997).
Does continuous visual feedback aid debugging in direct-manipulation
programming systems? In: Proceedings of the ACM SIGCHI Conference on
Human Factors in Computing Systems (CHI), pp. 258–265. ACM Press, New
York, New York, USA. Available at:
http://old.sigchi.org/chi97/proceedings/paper/mmb.htm. 2, 14