Structured concurrency for GUI programming without concurrency!

Yes, many GUI programs are programmed sequentially and I pretend it may be easier to program them with structured concurrency! (I made real implementations of that in FuncSug, see Memory example and Hypertext fiction example )

I advocate that adding a few new concurrency control flow structures to programming languages improves easiness of making GUI programs without (apparent) concurrency (It remains useful for cases with concurrency though).

I start with what we expect of a GUI program. Then I reach the need of reacting to user events, but how? I dismiss use of callback and propose this solution: control flow structures that give naturally structured concurrency.

What do we expect when running a GUI program?

I think we expect:

  1. that we get results
  2. that users participate
  3. that things happen
  4. that item 2 has an influence on item 3

In the beginning, in most programs,

  • Item 1 (getting results) was the most important
  • Item 2 was negligible: Users only participated when they supplied input data to the program
  • Item 3 was also negligible: Programs made only one thing to happen: supplying results
  • Item 4 was usually true but on negligible items (2 and 3).

Nowadays, many programs are GUI programs. In many of these, items 2 and 3 are very important: users very often participate and the programs make many things to happen to users (changes of display, sounds,…).

Here, I will forget item 1 and concentrate on items 2, 3 and 4.
So, I don’t consider neither declarative programming (which is particularly adapted for item 1) nor object oriented programming (because I don’t want to take data into account here).

My starting point is thus structured imperative programming.

User participations (item 2) is grossly what we call user events. Users expect that the program reacts (item 3 and 4) to what (user events) they do.

Reactions to user events

So, nowadays, GUI programs have to react to user events. This is what could be called “event-driven programming” (in a broad sense).

How can this be implemented? One solution is to mimic hardware solution (Interrupt handler), that is, write a callback (Event handler) for each expected user event.
That’s the solution that has been adopted in very many pieces of software.

Wait a minute! Do we always mimic hardware in software programming? Do we mimic JMP hardware instruction by writing GOTO?
No, nowadays, we have better: control flow structures (if, while, function,…). Why is this better? Because it’s nearer human thinking.

Is this possible for event-driven GUI programming?
How can GUI programming be nearer human thinking? For the users, multiple choices are brought to them in the same time. That is, these choices are brought to them in parallel. That is what could be called “logical parallelism (of possibilities)” or “concurrency (of possibilities)”.

So, I advocate use of new (logical) parallel constructs to express the multiplicity of choices a user has at times.

Control flow structures in structured imperative programming

Classically, in imperative programming, control flow structures are categorized:

  • sequence (implicit)
  • choice (if, switch, …)
  • loop (while, do_until, for, foreach, …)
  • subroutine (procedure, function, …)

They come with a few modifiers for loops:

  • break
  • continue

and a modifier for subroutine:

  • return

These modifiers refer implicitely to the enclosing loop/subroutine.

A new way to react to user events: the structured-ly concurrent way

I propose a few new control flow structures for that.

With logical parallelism, two additional control flow instructions are useful:

  • sequence (it already existed but, now, it have to be explicit)
  • parallel

No parallel branches may continue after the ‘parallel’ block is finished (Otherwise, it could not be qualified “structured concurrency”).
So, basically, the ‘parallel’ block finished only when all branches are finished.

But there are cases where some parallel branches becomes definitively useless at some point of time.
So, it can be useful to be able to stop them (definitively) easily.

When do some parallel branches have to be stopped?

1) When a certain number of branches are finished

There are cases where other parallel branches are nonsense when one (or more) is finished.
So, I added a syntax: parallel exitAfter N finished: (I could have named it parallel_race or parallel exitAsSoonAs).
In this case, when N branches are finished, the other ones are stopped (definitively) and the parallel block is finished.

This is handy when you want the user to have two possibilities:

  • wait N seconds
  • click a button
parallel exitAfter 1 finished:
	waitSeconds(30)
	awaitClick(myButton)

(see an example line 15 and test it)

2) When some branches are selected

There are also cases where you have to dismiss other branches as soon as one (or more) branch has passed a waiting for a certain user event.
So, I added a syntax:

parallel(select N):
	select:
		<first part of the branch> # As soon as this part is finished, the branch is described as "selected".
	do:
		<second part of the branch>
	...
	select:
		...
	do:
		...

As soon as N branches are “selected”, the other ones are stopped (definitively).

This is handy if you want to implement, for example, a hypertext fiction (see an example lines 22 and 28) or a memory game (see an example line 81 and test it).

parallel(select 2):
	select:
		awaitClickOnCard(1)
	do:
		animateCard(1)
		showCardFace(1)
	...
	select:
		awaitClickOnCard(N)
	do:
		animateCard(N)
		showCardFace(N)

It’s also useful to have the parallel counterpart of for i in list,

parallel(forEachValueOf chosenCard, select 2):
	select:
		awaitClickOnCard(chosenCard)
	do:
		animateCard(chosenCard)
		showCardFace(chosenCard)

You can find a real implementation of this there.

Modifiers

These control flow structures come with a labeling system and a few modifiers:

  • break
  • restart
  • pause
  • resume

These modifiers refer explicitely to a labeled block (of any kind).

break and restart

  • break interrupts the block definitively which is then considered finished
  • restart restarts the block

(see an example lines 113 and 121 and test it)

pause and resume

  • pause freezes the block
  • resume unfreezes the block

(see an example lines 104 and 106 and test it)