archives

The future of live programming

I'm at a crossroads in my research...my APX demo at strangeloop went well, and I learned a lot about live programming in the process. Hitting a target with a water hose and Hancock's steady frame (the visual information to guide one's aim) are very much key, and must be the focus of this work. Bret Victor's ideas in his learnable programming essay are nice and useful, but Chris Hancock provides the underlying reason on why they are so. In particular, scrubbing is a huge complement to live feedback, but so is direct manipulation when it can be managed, as well as the ability to abstract from a concrete implementation with concrete values into a general implementation. Its all about guided aiming.

On the one hand, what we have now is already kind of useful: at least having access to the execution while editing so you can just inspect anything you want at any time is a big advancement over dead programming. But on the other hand, the more powerful features, like scrubbing, are very limited to constants and simple abstractions that are easily placed in a continuum. They are not useful in general: abstractions are not easily placed in a continuum, and anyways have effects and outputs that are not intrinsically visual. If programming is to be revolutionized, we have to break out of that "2D box" into general usefulness.

So there is a lot of work can provide some inspiration on where to go now:

  • Alexander Repenning's Conversational Programming. Basically, what if code completion could be augmented by execution context? Then you could more easily go from the "program you have" to the "program you want." It is still very visual, but it doesn't seem to be a very big stretch to make it general, leading us to...
  • Joel Galenson's CodeHint system, which provides the results of a Java method call in the code completion call, allowing for more informed selection.
  • Gilad Bracha also argues that methods should be made live during programming, that is development should be informed by live executions. I used to think that this wouldn't be good enough, but I'm now coming around to this view, at least partially (incremental re-execution can make for better feedback loops, managed time makes reactivity easier and allows for time travel/strobing, but none of that is necessary...). Heck, if you have values, you don't even need types to get good code completion, you can get better code completion even since you can inspect a specific run-time state!
  • Conal Elliot's tangible functional programming. Automatically creates tangible interfaces for values (including functions), but they retain their abstractness.

One aspect that is kind of cool in APX is the ability to manipulate certain values in the visual view, and use that to create abstract-specific graphical interfaces that are used during programming; e.g. adjust the velocity of a ball as a vector directly in the editor. Nice, but I think this notion of graphical programmer interfaces (GPIs) can be applied more generally with the caveat that these interfaces feed on specific execution contexts. Text doesn't go away as text is abstract and concise, which is necessary in building scalable programs. But having graphical assists while editing code and debugging is quite useful.

So here are some driving examples to guide the next step that I've thought about so far:

  • Consider writing a lexer. Given a variable x whose value is 'f', you want to build a condition that is true on 'f'. You bring up the code completion menu for members on x, and specify that you want only those members who evaluate to 'true', leaving you with a smaller set of members to choose from, including "isNonSpace", "isAlphaNumeric", and "isAlpha".
  • Still writing a lexer, you have the string str whose value is "foo+ bar", you bring up code completion on str, select "count". Now you go to fill in count's argument, a predicate. Depending on what predicate you chose, count returns a different value, if you scrub the desired value of count to 3, you get fewer choices for this predicate, including "isAlphaNumeric" and "isAlpha". (Alternatively we could figure out how to do this in one action, since you might not want to commit to "count" until you figured out it had an argument you could use)
  • Consider accessing the element of a dictionary, code completion for the key can show the literal values of the keys (and only those keys actually in the map), along with the values they evaluate to. Selecting a desired key, you can then "pick" some abstract expression that meets that key's value, thus making the code more abstract (we are programming against specific execution contexts, but we are still programming for the abstract!).
  • Substring can allow you to select directly the part of the string that you want from an input string. You can then abstract those indices if necessary by finding expressions that compute them.
  • As a simplification of the type state problem, code completion on a file shows "open" if the file's execution state is not open, or "close" if the file's execution state is "open". This is of course not "safe" for all execution contexts but it does allow one execution context to narrow the options (and it has to be safe for at least that context).
  • When opening a file from a string, a file picker can come up to specify that string (this can be abstracted later as desired).
  • Transforming a text file into something the program can use can be done with the aid of a GPI that knows what the text file is. So say you get:
    1.23,  45.6, 7.0
    5.734, 56.1, 0.66
    10,    0.8,  1.2
    

    The GPI can allow you to split it into rows via new lines (just select a visible NL character), split the rows into columns via the comma (select the comma), and it knows that the toDouble API will work for any column (and other string conversions won't work). This leads to a very efficient and safe file transformation process (with the caveat that it applies to only one execution context).

This really needs to work in two ways. First, a GPI can show you where you can go, and provide feedback about the consequences of going there. Second, we often need to work backwards from a concrete value to come up with an abstract expression that produces it. E.g. x - 30 or x / 2 where x is 60. The latter is much more difficult since it leaves the API open (the former has committed to an API), and their could be multiple APIs involved! For example, let's say I know I need the number 42 for the specific execution context, but to get there, I need to plug "foo" into dictionary "d" and access the bar field of that. Ugh. Even worse if we need to come up with a general arithmetic expression with very continuous inputs! Still, the connections needed to abstract a concrete value will often be simple (e.g. I need "42", x is just "42", use x), so perhaps it is just something used but isn't always usable. Also, there is not a good story yet for defining abstractions in a live programming context...e.g. new kinds of objects, ones that can remember some state to be used somewhere remote, or a new method. Perhaps this is also part of the create by abstracting process, I'm not sure.

(Ok, I wrote much more than expected, apologies!)