A GUI controller

Suppose we need a simple program to look up items in a database, based on a search string.


The user can enter a search string in the text field and then press the Go button. This will at first put a “Searching…” message in the text area at the lower part. Then the actual search will be performed at a database, which may take a few seconds. Finally the results from the database are shown in the text area. In SubScript you can program that sequence of events and actions in an intuitively clear way:

  searchSequence = searchCommand  showSearchingText searchInDatabase showSearchResults

Here searchCommand would represent the event of the user pressing the button. showSearchingText and showSearchResults each write something in the text area. searchInDatabase does the database search. searchCommand is refined with a reusable script named clicked:

  searchCommand  = clicked(searchButton)

This clicked script “happens” when the user presses the search button. It is defined in a utility object subscript.swing.Scripts. As a bonus, the action script makes sure the button is exactly enabled when applicable. It will automatically be disabled as long as searchInDatabase is going on. The definition of clicked is also marked as implicit so that its name may be left out:

  searchCommand = searchButton

This states as concise as possible that clicking the search button triggers he search. The script calls showSearchingText and showSearchResults set the text contents of the text area, which is represented by the variable namedoutputTA. A complication is that this must happen in the swing thread:

  showSearchingText = @gui: {outputTA.text = "Searching: "+searchTF.text}
  showSearchResults = @gui: {outputTA.text = ...}

Here @gui: is again an annotation': gui is a method in subscript.swing.Scripts, that has the special value here as a parameter. This the code of this annotation is executed on activation. It makes sure that its operand (the code fragment) is executed in the swing thread, just as needed. The searchInDatabase could in a similar way perform a search on the database in a background thread. In this example, the search is simulated by a short sleep, but still in a background thread, so that the GUI will not be harmed during the sleep. A nice looking way to specify that an action must happen in a background thread is by enclosing it in braces with asterisks:

  searchInDatabase = {* Thread.sleep 3000 *}

If you would to program this functionality in plain Java, the resulting code will be much more complex. The code would look like:

private void searchButton_actionPerformed() {
  outputTA.text = "Searching for: " + searchTF.text;
  searchButton. setEnabled(false);
  new Thread() {
    public void run() {
      Thread.sleep(3000) //i.e. searchInDatabase
      SwingUtilities.invokeLater(
        new Runnable() {
          public void run() {
            outputTA.text = "Search ready";
            searchButton. setEnabled(true);
          }
        }
      );
    }
  }.start();
}

In Scala it would be much similar:

val searchButton = new Button("Go") {
  reactions.+= {
    case ButtonClicked(b) =>
      enabled = false
      outputTA.text = "Searching for: " + searchTF.text
      new Thread(new Runnable {
        def run() {
          Thread.sleep(3000) //i.e. searchInDatabase
          javax.swing.SwingUtilities.invokeLater(new Runnable {
            def run() {outputTA.text = "Search ready"
                       enabled = true
          }})
      }}).start
  }
}

For a good comparison of sizes, the SubScript version without refinements is:

live =       searchButton
       @gui: {outputTA.text="Searching for: " + searchTF.text}
             {* Thread.sleep(3000) *} //i.e. searchInDatabase
       @gui: {outputTA.text="Search ready"}
             ...

Extending the program

It is easy to extend the functionality of this program. For instance, the search action may also be triggered by the user pressing the Enter key in the search text field (searchTF). For this purpose we can adapt the searchCommand. Another user command could be to cancel an ongoing search in the database. For this the user could press a Cancel button, or press the Escape key. Finally the user may want to exit the application by pressing an Exit button, rather than clicking in the closing box. Moreover, he should then be presented a dialog where he can confirm the exit.

  searchCommand = searchButton + Key.Enter
  cancelCommand = cancelButton + Key.Escape
  exitCommand   = exitButton   + windowClosing

Here the plus symbol denotes choice, just like the semicolon denotes sequence. There are quite some other operators like these, most of which express a specific flavour of parallelism. windowClosing is a predefined script. Key.Enter and Key.Escape stand for calls to implicit scripts key and vkey. We see that searchCommand has been defined as an addition of a button and a character. This is something new in programming; I call it ”Item Algebra”, see later. After the exit command, a confirmation dialog should come up, in the Swing thread. If the user confirms, then the program should end. After the cancel command, an applicable text should be shown in the text area:

  exit         =   exitCommand @gui: while (!confirmExit)
  cancelSearch = cancelCommand @gui: showCanceledText

This exit script is easily added to the live script:

  live         = ...searchSequence || exit

The double bar || denotes or-parallelism: both operands happen, but when one operand terminates successfully the parallel composition also terminates successfully. The double bar is here analogous to its usage in boolean expressions. The same holds for 3 other parallel operators:&&|and &.The 3 dots (...) are equivalent to while(true). Two dots (..) would create a loop with an optional exit, a bit comparable to the asterisk in regular expressions. To fit in a call to the cancelSearch script, we have to split up the script searchSequence:

  searchSequence = searchCommand
                   showSearchingText searchInDatabase showSearchResults / cancelSearch

This way the Cancel button will only become enabled after the search command has been issued, and it will be disabled again after the search results have been shown.The slash symbol (/) denotes breaking behavior: the left hand side happens, but the right hand side may take over.

Note that a newline was inserted; as in plain Scala this is like inserting a semicolon; it makes sure that the left hand side of the slash operator extends only to showSearchingText. Therefore the cancel button will only be enabled after the search command has been given.

Adding a progress monitor

Suppose we want some progress indication while the background search is ongoing.
For this we can add a progress monitor process in parallel to the search action:

  searchInDatabase  = {*sleep(5000)*}||progressMonitor

  progressMonitor   = ... @{gui(there)}:{outputTA.text+=here.pass} {*sleep(200)*}

We use or-parallelism so that the monitor deactivates as soon as the search is over. The monitor itself is an eternal loop, so it will not end the search prematurely. Inside the loop, the monitor adds some text, the loop counter here.pass, to the output text field and then sleeps a short while.

Guarding the search command

Another improvement is to enable the Go button only when the search button is not empty.
This is possible by inserting a guard just before the search command:

  searchSequence = searchGuard searchCommand
                   searchAction / cancelSearch

  searchGuard    = guard(searchTF, ()=> !searchTF.text.isEmpty)

This searchGuard is a script that may well be used in other applications; therefore it is included in the utility object subscript.swing.Scripts. Internally it is a loop that checks on any event in a given UI control; if a given condition is met, an optional exit out of the loop is available:

  guard(comp: Component, test: ()=>Boolean) = if test() then .. else ...; anyEvent(comp)

In this context, an optional exit to the loop implies that the call to searchCommand is activated; otherwise not. Meanwhile the searchGuard stays active as well: it expects another event after which it will again check the input text field, etc.

Reasoning

The algebraic nature of SubScript facilitates reasoning about program behavior.
A use case is the searchGuard. Using axioms we hope to be able to show that this really works correctly.

Suppose x and y are actions or scripts, but not special elements such as . and … .
Then we may say that for the expression with optional exit
x . y is the solution of the recursive equation X = x(1+y).

Likewise
x..y is the solution of the recursive equation X = x(1+yX)
x...y is the solution of the recursive equation X = xyX

x; if t then .. else ...; y would be the solution for

X = x( (if t then 1) + yX)

We could abbreviate if t then 1 to t. Since t is a boolean it will either evaluate to 0 or 1, which are valid elements of process expressions.
So the equation is X = x(t+yX).

Now instead of the rather long script names and the explicit test on the text field, it is handy to use shorthand notations:

t – the test whether text field is non-empty
e – any event in the text field
c – the search command
a – the search action, optionally disrupted by the cancellation
G – the guard
S – the search sequence

So let’s see what the search sequence does. Substitute the contents of the searchGuard; then

S = G c a
G = t + e G

Substitute the definition of G in S:

S = (t + e G) c a
  = t c a + e G c a

Note that the previous line ends in the synonym of S, so

S = t c a + e S
  = (if t then 1) c a + e S
  = (if t then (1 c a)) + e S
  = (if t then c a) + e S

In words the search sequence is:

  • if the text field is non-empty then the search command may happen, followed by the search action
  • or in any case there may be an event on the text field, and then we are back at the start

Conclusion: the guard in context does what we hoped it would do.

Note: the foregoing is not a formal proof; for that precise semantics of constructs such as .. should be defined in ACP terms. This is still to be done.
We also included if-constructs and boolean expressions in script expressions. In pure sequential contexts that will probably be OK, but when concurrency is allowed, the exact evaluation time will be relevant.

Further reading:
“Process Algebra with Guards – Combining Hoare Logic with Process Algebra (Extended Abstract)” by Jan Friso Groote and Alban Ponse, in CONCUR ’91 Proceedings of the 2nd International Conference on Concurrency Theory, Springer.

Leave a Reply