We usually think of programming as an inherently complex activity entrusted to the care of skilled engineers. A goal of programming language research is to find ways to tame this complexity and make programming easier. The field of end-user programming comes at things from the other direction, aiming to bring to end- users and non-experts some of the power of programmers. Interactive programming puts “programming” and “using” on a continuum: programming is the manipulation of a running application in order to change its behaviour, and every user, in principle at least, has access to this capability. In practice, not every end-user will have the inclination, or authority perhaps, to make fundamental changes to the logic of an application, but no part of the application is fundamentally hidden or immutable. (We discuss the importance of access control in Future Work, §7.2.3.)
End-user programming is a broad and heterogeneous topic which we will only briefly summarise, because the connection to interactive programming is mostly conceptual. The first end-user programming systems were spreadsheets (§3.8, above), which brought some of the power of programming to business users with accounting rather than programming expertise. Spreadsheets and other office applications offer further end- user customisation and automation via macros which the user creates by “recording” an interaction they have with the GUI into a scripting language like Microsoft’s Visual Basic for Applications [Sep00].
More generally, if the system supports programming by example, the topic of Halbert’s PhD thesis [Hal84], then it may be able to extract a simple program from an exemplar performance of a task provided by the user. The influential 1984 Tinker system of Lieberman and Hewitt [LH80] had an interesting interactive approach to programming by example. The user built new procedures by working out individual steps of the procedure in concrete situations. Tinker displayed the value computed by an individual step as it was performed; when this value was then used in a subsequent step, the code associated with the computation of that value would automatically be incorporated into the new step. Edwards’ example-centric programming is a more recent incarnation of the idea [Edw04]. Our approach to interactive programming may suit this style of programming by example, because of the way we attribute the parts of a computed value to individual steps of the computation and always situate the user at a concrete execution of a function, rather than at a
static function definition.
Truchard and Kodosky’s 1987 LabVIEW system brought the convenience of spreadsheets to scientists and engineers [WT96]. Their system allowed a user with domain expertise, but not necessarily programming expertise, to build configurations of “virtual instruments”, using a graphical dataflow language extended with a looping construct. Both spreadsheets and LabVIEW are examples of the importance of domain-specific languages to end-user programming. Visual programming languages (§3.6, above) also commonly feature in end-user programming solutions because they can easily be customised into domain-specific programming interfaces. Repenning’s AgentSheets is a spreadsheet-like visual programming system which allows non- expert users to build interactive, web-based simulations [Rep93].
The most well-known form of web-based end-user programming is the mashup. A mashup is a web application, consisting mostly of plumbing, built out of existing web services, web forms and online data sources. Mashups can do things like retrieving data from various sources, processing it, and publishing the result as a feed; or adding and removing various widgets from a website to change its appearance and then performing some data entry on one of its forms. Some recent mashup development environments are surveyed by Grammel and Storey [GS10]. One problem they identify is that mashup tools often lack support for basic programming activities like testing and debugging.
Shortcomings like these are, we believe, an inevitable consequence of treating end-user programming as a second-class activity distinct from “real programming”. By contrast, interactive programming puts “programming” and “using” on a continuum. This has the potential to transform how we approach end-user programming. One half of the story is that programmers themselves would often welcome being able to move seamlessly between the roles of programmer and end-user. A programmer unfamiliar with a language or application domain should be able to learn about it by playing with an existing application, interactively disassembling it to understand how it works, eventually changing it and adding new features. Programming often starts as using. Equally, testing is a form of using. When something goes wrong, the programmer should be able to diagnose and fix the problem “in situ”, without having to restart the test case in a debugger. The other half of the story is that an end-user unfamiliar with programming should be able to progressively discover details about the inner workings of an application. As they gain confidence, they can experient with simple customisations, and eventually move into task automation and full-blown development. A user should be able to grow into a programmer.
4
Reifying Computation
In interactive programming, computations are “self-explaining”, meaning they can be opened up and inter- actively explored. Execution is reified: the activity of execution has been transformed into a description of that activity. This process of taking a dynamic process and transcribing its behaviour into a static record is conventionally called tracing.
The term “tracing” does not convey quite the end-user intuition we have in mind. “Tracing” over- emphasises the dynamic process – a trace must be a trace of something. We would prefer the user to think of the spatially extended structure as the execution. But for the sake of clarity, we adopt the termi- nology of traces in this thesis, although we also sometimes treat “computation” and “reified computation” as synonymous.
As discussed in Related Work (§3.1above), tracing is widely used for debugging and other offline dynamic analyses. There are several forms a trace might take depending on the semantics of the language in question. A trace can be built by instrumenting the program to be traced or by instrumenting the interpreter in which it runs. Once obtained, an execution trace can be explored as a static structure or replayed to recover the original dynamic behaviour. For LambdaCalc, we use a big-step reference semantics (§4.1); traces take the approximate form of big-step derivation trees (§4.2); and we build them using a tracing interpreter (§4.3). Our approach to tracing is not particularly novel but is the foundation for Chapters5and6.
Types τ ::= 1| b | τ1+ τ2| τ1× τ2 | τ1→ τ2| µα.τ | α
Variable contexts Γ ::= • | Γ, x : τ
Expressions e ::= x | () | c | e1⊕ e2| fun f (x).e | e1e2
| (e1, e2) | fst e | snd e | inl e | inr e
| casee of {inl(x1).e1; inr(x2).e2} |
| rolle | unroll e
Values v ::= c | (v1, v2) | inl v | inr v | roll v
| hρ, fun f (x).ei Environments ρ ::= • | ρ[x 7→ v]