parent previous next question (Smalltalk Textbook 27)

Continuation

'Continuation' is a program control structure which is related to Scheme and Prolog. Assume we have 3 routines (main, sub1, sub2) and one process. Main (1) prints 'VisualWorks' on the transcript, (2) calls sub1, (3) prints 'GNU Smalltalk'. Sub1 (1) prints 'ObjectWorks', (2) calls sub2, (3) prints 'SmalltalkAgents'. Sub2 prints 'ObjectWorks'. And a process executes main. So we have a simple program to print the strings 'VisualWorks', 'ObjectWorks', 'Smalltalk/V', 'SmalltalkAgents', 'GNU Smalltalk' in turn using main, sub1, sub2.

Program 27-1 shows a simple way to do it.


Program-27-1: (BlockClosure, Process; terminate, resume, newProcess)
--------------------------------------------------------
| process sub2 sub1 main |
process := nil.
sub2 :=
  [Transcript crtab: 3; show: 'sub2 begin'.
  Transcript crtab: 3; show: '|-- Smalltalk/V --|'.
  Transcript crtab: 3; show: 'sub2 end'].
sub1 :=
  [Transcript crtab: 2; show: 'sub1 begin'.
  Transcript crtab: 2; show: '|-- ObjectWorks --|'.
  sub2 value.
  Transcript crtab: 2; show: '|-- SmalltalkAgents --|'.
  Transcript crtab: 2; show: 'sub1 end'].
main :=
  [Transcript crtab: 1; show: 'main begin'.
  Transcript crtab: 1; show: '|-- VisualWorks --|'.
  sub1 value.
  Transcript crtab: 1; show: '|-- GNU Smalltalk --|'.
  Transcript crtab: 1; show: 'main end'].
process :=
  [Transcript cr; show: 'program begin'.
  main value.
  Transcript cr; show: 'program end'.
  process terminate] newProcess.
process resume
--------------------------------------------------------

I've expressed routines as block closures and used lazy evaluation of block closures for subroutine calls. The code is written so that it is easy to trace the execution of the subroutines. The output should resemble:

---------------------------------------------------------
program begin
        main begin
        |-- VisualWorks --|
                sub1 begin
                |-- ObjectWorks --|
                        sub2 begin
                        |-- Smalltalk/V --|
                        sub2 end
                |-- SmalltalkAgents --|
                sub1 end
        |-- GNU Smalltalk --|
        main end
program end
---------------------------------------------------------

The control structure of this program is known as call-return and is easy to understand because you can think of message sending as subroutine call. This control structure guarantees that a message sender (a caller) will receive a response when it sends the 'value' message to a block closure.

Program 27-2 uses the continuation control structure instead. Read the program and try to understand it.


Program-27-2: (BlockClosure, Process; continuation, terminate, resume, newProcess)
------------------------------------------------------------
| process sub2 sub1 main |
process := nil.
sub2 :=
  [:continuation |
  Transcript crtab: 3; show: 'begin sub2'.
  Transcript crtab: 3; show: '|-- Smalltalk/V --|'.
  continuation value.
  Transcript crtab: 3; show: 'end sub2'].
sub1 :=
  [:continuation |
  Transcript crtab: 2; show: 'begin sub1'.
  Transcript crtab: 2; show: '|-- ObjectWorks --|'.
  sub2
    value:
      [Transcript crtab: 2; show: '|-- SmalltalkAgents --|'.
      continuation value].
  Transcript crtab: 2; show: 'end sub1'].
main :=
  [:continuation |
  Transcript crtab: 1; show: 'begin main'.
  Transcript crtab: 1; show: '|-- VisualWorks --|'.
  sub1
    value:
      [Transcript crtab: 1; show: '|-- GNU Smalltalk --|'.
      continuation value].
  Transcript crtab: 1; show: 'end main'].
process :=
  [Transcript cr; show: 'begin program'.
  main
    value:
      [Transcript cr; show: 'end program'.
      process terminate]] newProcess.
process resume
------------------------------------------------------------

The program prints strings of 'VisualWorks', 'ObjectWorks', 'Smalltalk/V', 'SmalltalkAgents', 'GNU Smalltalk' in turn just like program 27-1. However something is different. If you compare the outputs, you'll notice that there are no 'end' messages of subroutines:

--------------------------------------------------
begin program
        begin main
        |-- VisualWorks --|
                begin sub1
                |-- ObjectWorks --|
                        begin sub2
                        |-- Smalltalk/V --|
                |-- SmalltalkAgents --|
        |-- GNU Smalltalk --|
end program
--------------------------------------------------

The reason is that once a subroutine is called, control never returns to the sender. Though 'end sub2', 'end sub1',' end main' are not printed, the program ends correctly. We tend to assume that when an object sends a message to another object, the sender will receive a response and then continue processing. But the above program does not act according to this expectation.

In the continuation control structure, the sender sends things that the sender needs to to do after receiver completes his job as a block closure to the receiver. Then the receiver does whatever it needs to as a last step it evaluates the block closure. Therefore the process of the program is terminated in sub2 and control is never returned back to the top level caller. The continuation control structure does not need to return as long as the process is continuing as expected.

Now let's see what happens when there is an error. Program 27-3 is a call-return control structure program which (intentionally) encounters an error in sub2, traps the error by signal and exception.


Program-27-3: (BlockClosure, Process, Signal; continuation, terminate, 
resume, newProcess, handle:do:)
-------------------------------------------------------
| process sub2 sub1 main |
process := nil.
sub2 :=
  [Transcript crtab: 3; show: 'begin sub2'.
  self errorSignal handle: [:exception | ]
    do:
      [Transcript crtab: 3; show: '|-- Smalltalk/V --|'.
      self error: 'Help me!'].
  Transcript crtab: 3; show: 'end sub2'].
sub1 :=
  [Transcript crtab: 2; show: 'begin sub1'.
  self errorSignal handle: [:exception | ]
    do:
      [Transcript crtab: 2; show: '|-- ObjectWorks --|'.
      sub2 value.
      Transcript crtab: 2; show: '|-- SmalltalkAgents --|'].
  Transcript crtab: 2; show: 'end sub1'].
main :=
  [Transcript crtab: 1; show: 'begin main'.
  self errorSignal handle: [:exception | ]
    do:
      [Transcript crtab: 1; show: '|-- VisualWorks --|'.
      sub1 value.
      Transcript crtab: 1; show: '|-- GNU Smalltalk --|'].
      Transcript crtab: 1; show: 'end main'].
process :=
  [Transcript cr; show: 'begin program'.
  main value.
  Transcript cr; show: 'end program / abort program'.
  process terminate] newProcess.
process resume
-------------------------------------------------------

The transcript will show the same output as Program 27-1:

-----------------------------------------------
begin program
        begin main
        |-- VisualWorks --|
                begin sub1
                |-- ObjectWorks --|
                        begin sub2
                        |-- Smalltalk/V --|
                        end sub2
                |-- SmalltalkAgents --|
                end sub1
        |-- GNU Smalltalk --|
        end main
end program / abort program
-----------------------------------------------

The program dose not recognize an error because the message sender does not check the result of the call to the receiver. Now let's try the continuation control structure program. Program 27-4 also raises an error in sub2 intentionally, and traps it by signal and exception.


Program-27-4: (BlockClosure, Process, Signal; continuation, terminate, 
resume, newProcess, handle:do:)
-------------------------------------------------------
| process sub2 sub1 main |
process := nil.
sub2 :=
  [:continuation |
  Transcript crtab: 3; show: 'begin sub2'.
  self errorSignal handle: [:exception | ]
    do:
      [Transcript crtab: 3; show: '|-- Smalltalk/V --|'.
      self error: 'Help me!'.
      continuation value].
  Transcript crtab: 3; show: 'end sub2'].
sub1 :=
  [:continuation |
  Transcript crtab: 2; show: 'begin sub1'.
  self errorSignal handle: [:exception | ]
    do:
      [Transcript crtab: 2; show: '|-- ObjectWorks --|'.
      sub2
        value:
          [Transcript crtab: 2; show: '|-- SmalltalkAgents --|'.
          continuation value]].
  Transcript crtab: 2; show: 'end sub1'].
main :=
  [:continuation |
  Transcript crtab: 1; show: 'begin main'.
  self errorSignal handle: [:exception | ]
    do:
      [Transcript crtab: 1; show: '|-- VisualWorks --|'.
      sub1
        value:
          [Transcript crtab: 1; show: '|-- GNU Smalltalk --|'.
          continuation value]].
  Transcript crtab: 1; show: 'end main'].
process :=
  [Transcript cr; show: 'begin program'.
  main
    value:
      [Transcript cr; show: 'end program'.
      process terminate].
  Transcript cr; show: 'program abort'] newProcess.
process resume
-------------------------------------------------------

The result of the program is as follows.

----------------------------------------------
begin program
        begin main
        |-- VisualWorks --|
                begin sub1
                |-- ObjectWorks --|
                        begin sub2
                        |-- Smalltalk/V --|
                        end sub2
                end sub1
        end main
program abort
----------------------------------------------

The program is aborted, and control returns from sub2 to sub1 and back to main. The reason we know it was aborted is because we see the 'end sub1' etc messages. Control does not return to the message sender if the process ends normally, but once an error is detected, control returns to the message sender. You do not have to program anything to get this behavior. The system does this automatically. This is a powerful feature of the continuation control structure.

Program 27-5 is a call-return control structure program with the same effect as program 27-4. The message sender expects the 'value:' message to return with a value. It then checks that return value.


Program-27-5: (BlockClosure, Process, Signal; continuation, terminate, 
resume, newProcess, handle:do:)
---------------------------------------------------------
| process sub2 sub1 main |
process := nil.
sub2 :=
  [| result |
  Transcript crtab: 3; show: 'begin sub2'.
  self errorSignal handle: [:exception | result := false]
    do:
      [Transcript crtab: 3; show: '|-- Smalltalk/V --|'.
          self error: 'Help me!'.
          result := true].
  Transcript crtab: 3; show: 'end sub2'.
  result].
sub1 :=
  [| result |
  Transcript crtab: 2; show: 'begin sub1'.
  self errorSignal handle: [:exception | result := false]
    do:
      [Transcript crtab: 2; show: '|-- ObjectWorks --|'.
      sub2 value
        ifTrue:
          [Transcript crtab: 2; show: '|-- SmalltalkAgents --|'.
          result := true]
        ifFalse: [result := false]].
  Transcript crtab: 2; show: 'end sub1'.
  result].
main :=
  [| result |
  Transcript crtab: 1; show: 'begin main'.
  self errorSignal handle: [:exception | result := false]
    do:
      [Transcript crtab: 1; show: '|-- VisualWorks --|'.
      sub1 value
        ifTrue:
          [Transcript crtab: 1; show: '|-- GNU Smalltalk --|'.
          result := true]
        ifFalse: [result := false]].
  Transcript crtab: 1; show: 'end main'.
  result].
process :=
  [Transcript cr; show: 'begin program'.
  main value
    ifTrue: [Transcript cr; show: 'end program']
    ifFalse: [Transcript cr; show: 'abort program'].
  process terminate] newProcess.
process resume
---------------------------------------------------------

The program accomplishes the same result as the continuation version of program 27-4. However, to do this you must check the return value of the message receiver. This programming style is awkward and too verbose.

----------------------------------------------------
begin program
        begin main
        |-- VisualWorks --|
                begin sub1
                |-- ObjectWorks --|
                        begin sub2
                        |-- Smalltalk/V --|
                        end sub2
                end sub1
        end main
abort program
----------------------------------------------------

I described the difference between call-return control structure and continuation control structure. The continuation control structure is useful for back tracking or undoing, for example an algorithm which purposely does trial and error.

It's easy to code error handling, namely right after any call. If things go smoothly, the caller process is terminated before executing any of the error handling code. Continuations also make it easy to handle side effects produced by a routine called in the called routine. You send 'handle: exceptionBlock do: doBlock' to 'Signal' to handle an error which occurs in the 'doBlock' of 'exceptionBlock'. This way the handler need not be coded in the message sender, but instead can be coded at higher position in the calling tree, where it might be more appropriate. This maintains good modularity.


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves