No silver bullets (part 2)
Continuing to explore paradigms for end-user programming interactions
This is a continuation of my previous post, where I start looking at differences between interaction techniques defined by Nardi in Chapter 4 of A Small Matter of Programming. Make sure to check that post first if you haven't yet!
Interaction Techniques for End User Application Development (continued)
In this section we'll look at two more techniques. In each of these, Nardi looks the expectations for both of these interactions and provides a better alternative for each one. With how much stuff there is to talk about, I'll leave the final technique and some summarizing thoughts in a part 3.
Programming by Example Modification
The description of "programming by example modification" describes what we refer today as "templates"—predefined programs that can be modified by users to fit their needs. In this technique specifically, Nardi is referring to code templates.
One of the problems with relying on this approach with code templates is the same one we discussed in "Tasks, not bits", that being that people don't need to know how computers work to program their tasks. However, this is largely not a concern today as this is a commonly accepted notion in end-user programming explorations.
So, to contextualize this technique for 2021, let's assume we're working with a theoretical interface that doesn't require users to code, and the templates we're referring to are examples of this theoretical interface in different scenarios. The second problem Nardi describes (I believe) still applies to this format—how do you determine which examples are appropriate for your use case?
There seems to be some level of convergence around some use cases—if your end-user programming tool is good at creating systems of interfaces, you might lean towards "wikis" or "documentation" templates. If your tool is good at representing complex data, maybe you provide some form of "analysis" or "dashboard".
However, there's quite literally infinite permutation of programs you could build, and trying to put together some library to connect them all just makes searching through them more difficult. Airtable has a version of this—it's useful, but intimidating.
Furthermore, a template based system where users just modify existing templates takes away the agency that comes with end-user programming. You aren't really giving end-users computation power if their only options are to modify what already exists.
True end user programming allows users to build a meaningful application without reliance on obtaining code from other more sophisticated users. ... If it is impossible to begin a program without an existing program, the user is denied real control over the computation environment. (p. 70)
Nardi suggests that rather than provide templates that users need to edit, we should aim to build reusable modules that can be used in a variety of contexts.
This is actually a development that has grown quite significantly since Nardi's book was published. As I had mentioned in "Tasks, not bits", reusable software components have developed in a variety of ways. Though these components are mainly libraries of code and can only be used by developers, the trend with more recent developments indicate we're getting closer and closer to higher-level (non-code) reusable components.
Frameworks like Next and Gatsby have been build on top of existing high level frameworks like React, drastically reducing the knowledge overhead needed to spin up a website. Tools like Webflow and Figma take the opposite approach, allowing designers to "compile" designed layouts in code.
However, my hunch is that the biggest hurdle preventing higher-level software reuse in this interface area will be the lack of cooperation incentives for the companies that build these tools. For example, if two companies have high-level abstractions in their products that allow users to re-use modules they built, neither one has any incentive to allow these modules to be used in another product—at least not directly. Not only would it be costly for these companies to build some universal adapter into their own product, but doing so would give their competitors "access" to their users.
Of course, this is a reductive analysis for sake of simplicity—I am sure there exists some incentive mechanisms for cooperation, but at the time of writing this, none seem to be gaining traction quickly enough to keep up with the development of end-user programming tools. Hopefully I get proven wrong though!
TL;DR for this technique: Templates trade agency for ease. Re-usable modules are a great alternative, but face challenges when they transcend the level of code
Programming by Example
This one is named a little confusingly, especially since we just had "programming by example modification". They aren't really related at all, despite their similar names.
The "examples" in the previous section are generated by some external author, either the makers of the tool or folks who can share their knowledge on a tools best practices. Here, the "examples" are created by the user.
The core idea by "programming by example" is that when doing certain actions, we often repeat tasks over and over again. Repeating is something that can be done by a computer easily (relative to people), so we should try to infer repeated behavior when possible. For this to happen though, a computer needs to have an "example" action to work off of, hence the description "programming by example".
This actually exists in many products today, both on the micro and macro level. On the micro level, In MS Excel and GSheets, you can easily replicate cells through a simple dragging interaction:
And in design tools like Figma, you can repeat the properties of a duplication by hitting Command + D:
On the macro level, tools like Clay (yes, I am plugging Clay once again) allow you to apply the same set of actions to different groups of data by using columns in a spreadsheet metaphor: what gets applied to one row, gets applied to all rows.
Nardi mentions two stipulations with the process of inferring user behavior—termination (stopping things) and branching (changing actions taken based on data) are difficult. While patterns for termination have emerged over the years (both examples above use the lack of discrete inputs to terminate, with the tradeoff of quantity), branching remains a difficult challenge.
Branching behavior is complex as it not only requires preparing the potential "branches" of behavior, it also requires defining what conditions cause this behavior to branch. If a system is inferring branching behavior from an user-inputted example, either
the example itself would have to contain some notion of how repeated iterations should branch, or
the system would have to have a deep enough understanding of branching for the type of example it’s given.
Nardi notes here that you probably realize that this is no easy task.
It is difficult to imagine a robust general programing by example facility for handling conditionals. ... The wide scope of possible conditions relevant to performing a given action, as well as the difficulty of instructing the system about conditionals through concrete examples, argue against the general feasibility of programming by example systems supplying conditionals. (p.73)
Okay, branching is difficult, but let's assume some robust interaction pattern for branching emerges in the future. There still lies a larger problem with inferring from examples—people make mistakes!
A process that infers what to do from user input would have to account for imprecise input and errors. Of course, there's the argument that users should just learn how to input more precisely to use these actions, but I personally don't think this argument works for end-user programming tools, much less any software outside of certain use cases (expert tools that require precise input for conveying specific/granular intention e.g. music production, photo editing).
People are fallible—we make errors. To expect users to perform perfectly goes against the principles of why we're designing end-user programming tools. It's relevant that user input is defined enough to be captured, but the fidelity of this input shouldn't be the crux of how end-user programming intention is conveyed.
Nardi suggest we simply just give users the ability to command the system, rather than try and infer high level behavior. This sounds straightforward—that's because it's familiar to us already. Most tools that have been built since the 1990s pretty much all operate around this paradigm of "here are your tools for interacting in this system, use them at your discretion".
However, I do believe that—while this is a better approach (when compared with inferring)—this paradigm has been overused to the point where certain features simply "exist" in software, usually in some global dropdown menu, without any context as to when they're relevant.
Somewhere between inferring and entirely command driven interfaces is an area where you can weave in the right commands at the right time, allowing users to manifest their intentions in the system. By properly surfacing these interactions in the right context, we can reduce the need to "learn" a system from the ground up. The key to identifying which interactions we surface? Tasks! (the Tasks, not bits chapter is really important to this book!)
Commands that map as closely as possible onto the task-specific operations that users want to perform in their applications will support users at their level of skill and interest. Task-specific commands avoid the problem of being at such a low level that detailed, unfamiliar programming knowledge is required. (p.77)
Of course, identifying these commands is much more easily said than done, so that's why it's important to understand domain knowledge and system design principles (if you plan on designing end-user programming tools).
TL;DR for this technique: Intention over inference—people understand themselves more than computers understand them, computers just need to meet them halfway (not literally though)
These two examples were a little chunkier than I thought they'd be! We'll look at a very high-level interaction concept (automatic program generation) and summarize the main takeaways from this chapter for 2021. See ya then!