Declarative, Reactive Command Line

A good command line interface allows its users to pack a lot of useful work into strings the size of twitter messages. There is no room for boiler-plate, no patience even for parentheses. Identifiers must be short. Parameters must have good defaults. Failure should usually be obvious and safe, allowing users to refine their commands and try again.

A common property of most command-line interfaces today: they are of an imperative, procedural nature. It is difficult to declare the answer before prompted, which ties the model to time. The querying app must often wait for a prompt to be answered – i.e. interactive rather than reactive. It is difficult to modify an answer to an earlier query (e.g. “What is your name?”). Queries on the system are volatile and must be polled to remain up-to-date. System behavior is sensitive to order of expression. A command expressed twice will typically result in two distinct behavior instances.

A declarative, reactive command line would have the following properties:

  • Behavior of system is declarative. That is, users declare how a system behaves with a fair level of idempotence (declaring a behavior twice doesn’t result in twice the behavior) and commutativity (order of declaration not so critical).
  • Behavior of system is reactive. Any earlier declaration of behavior may be revoked or modified.
  • Time is generally implicit, to keep lines short. It is possible to declare many behaviors as occurring at the same time, i.e. a batch update.
  • Relationship between user and application may be maintained through command line. Users may anticipate answers to prompts, and declare answers in advance. Users may change declared answer at any time.
  • Prompts to user are inherently reactive, not interactive, meaning the app does not implicitly wait. If app must wait for valid answer, this must be modeled explicitly in the app – if(bad_answer) then nothing else doCoolStuff.
  • Most output to user is also reactive and subject to change over time. I.e. “Hello, World!” might later change to “Hello, Console!”. This could be expressed quite naturally with graphical output or curses. If streaming, a less natural mechanism might be required – but it could at least be symmetric to how the user modifies declarations.

I envision such a system working a lot like a tuple space: CLI actions add or remove declarations from the space. A simple case might be declaration like `+lights` turning on the house lights, and `-lights` turning them back off. At least one service (the shell) is observing the space and searching for other services to fulfill each request, but I think it might be more interesting to run this in reverse: allow any number of observers to `interpret` the declarations when deciding their behavior, and attach the console to different tuple spaces as needed.

Prompts from the application are given a unique structural identity, e.g. of the form “com.example.hello.user_name”, and all prompts on the ID will return the same value, which can be changed over time. Prompts can carry a lot of extra meta-data, including query strings.

Properties such as filters, queries, histories, suggestions and tab-completion, macros, aliases, topics or switching, CSCW, and persistence would help modernize the shell. Structured text data rather than plain strings could improve filters and queries a great deal, and support level of detail. JSON may be a bit too verbose, but something like ML9 would allow adding just a little structure where necessary and implicitly annotating each declaration with metadata.

This entry was posted in Open Systems Programming, User Interface. Bookmark the permalink.

9 Responses to Declarative, Reactive Command Line

  1. Are there any protoypes?

    Inferno OS looks promising for prototyping at least

    • dmbarbour says:

      At the moment, I’m not sure what I’d do with the command line. I need some declarative, reactive service to command, first. I doubt my first effort will be an OS shell – more likely an application CLI.

      • Omake has File Alteration Monitor. Omake can automatically start compiler when source changes.

        There might be a (functional reactive database, reactive demand application server) daemon and shell might be a client for that stuff

  2. I agree with all your points, but have to note two things: I know I’m a fanboy, but you can’t go beyond where Lisp has gone, without going where Lisp *has* gone. 😉 The Lisp Machines user interface / Common Lisp Interface Manager is by many regarded as the (literally) last cry and “finest available wisdom” in command user interfaces, and should probably inform your design.

    Second, the implicit assumption that a reactive, continuous approach is actually applicable to *all* of computing is unproven. It may well work out, but it may also be a pipedream. Just saying.

    • dmbarbour says:

      I will study CLIM at your suggestion. It is not a subject I’ve studied before.

      I agree that a declarative, continuous approach is not suitable for all computing and behavior tasks. For example, a print job is something that should happen once then be forgotten – a naturally volatile, imperative concept. If you at two different times declare intention to print something, you probably do want twice the effect. For a print job, I might have the print service itself remove the request from a shared tuple space, thus turning the space implicitly into a command queue. Alternatively, there can be a shorthand way to tell the console to annotate the declaration with a unique identifier such that the print requests are distinct (perhaps `++print foo`, instead of `+print foo`). In this case, the tuple space acts more as a task log. I’m not sure which approach I prefer.

      Where necessary, developers will have access to constraint models, discrete animations, and other abstractions. It’s easy to animate a tuple space by adding expiration as meta-data, telling the space to automatically remove an element some time after it is no longer continuously maintained.

      I do not assume or argue that every activity should be shoehorned into a declarative, continuous model. Rather, I believe it to be a far better foundation model for orchestrating other systems… that the imperative bits and stack machines and random choice and such should exist in relative isolation, modeled atop the declarative system, secured by external capabilities, and inconvenient enough that the path of least resistance is to pipe back to the declarative, continuous model ASAP. The goal, after all, is to ensure the system as a whole can be reasoned about in a declarative, continuous manner.

      [I did, at least in pseudo-code, validate RDP against many scenarios where behavior is naturally discontinuous. Print jobs were one of them.]

  3. I agree with Charles. I don’t think you will benefit from studying CLIM, or even SLIME/Swank in GNU Emacs (SLIME/Swank is a Lisp agnostic protocol for hooking an IDE into any Lisp’s compiler). However, I do think some problem domains where SLIME/Swank have proven useful might be food for thought:

    GOAL (Game Object Assembly Lisp), by Andy Gavin, allowed developers to update various parts of a program on a test gaming server. For example, after exploding a character into pieces, resetting the entire game state or just part of the game state (such as putting the character back together, but clearing the character from the game and putting a new identical character back in there). The questions to ask are what sort of properties should the updated code have such that we don’t have to think too hard about what will happen when we want to push new code to the live system?

    In other situations, I have observed that what I really want is a monotonically increasing counter for each “session” created. For example, in web programming, say I want to version how I dispatch on a (URL, cookie, server-state (ex: conversation)) pair. We should be able to create an accord between the user and the program that they will always be able to talk to the interface they were talking to, until they explicitly stop talking to the program or the program explicitly says it won’t listen any further (expired service contract). A contract expiring, or a user disengaging from a contract is an inference that we can use to find and collect ‘garbage’ that is no longer being used to handle requests.

  4. Zo-Bo: “I don’t think you will benefit from studying CLIM”

    Care to explain why?

    • I mean the implementation is irrelevant compared to the use cases. Use cases are way more dynamic than architecture. You can always write new use cases, you can’t always change your architecture.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s