Skip to main content
1 of 2

Implementing Classes as Coroutines?

most OOP languages implement a class as a garbage-collected reference. however, a task (coroutine) based implementation may offer several advantages over traditional object references:

-- method calls are transfer points and not separate functions. as such a coroutine directly
intergrates method calls into its control flow.

--this in turn allows a coroutine type to declare a context-free grammar for valid sequences of method calls which can be statically checked on the server end and is checked at runtime on the client end with a simple parsing table.

-- polymorphism is then implemented simply by intercepting the transfer points. this may provide a much more flexible polymorphism than OOP dynamic dispatch (which gets cumbersome if theres >1 dispatch parametre).

-- a coroutine instance is returned to the caller at runtime as a dialog object which contains the current schema node and exchanges parametres and returns on each continuation point. this object is also a de facto weak reference to the coroutine object (the dialog is allowed to close if its on a final node).

as an example of how such a type might work, ill sketch out a window using a pseudo-ada syntax:

    cotask interface window is
   -- the schema declares a context-free grammar for the valid sequences of method calls 
   -- on a window. transfer points (accept branches) in an implementor must follow the
   -- syntax. this can easily be checked at compile-time if certain rules are applied.

   schema is 
      start = createWindow drawWindow {stuff} closeWindow;
      stuff = doThisWithTheWindow | doThatWithTheWindow;

   -- entries (methods) are treated as terminals in the syntax above. the exported entries
   -- are the alphabet of the schema syntax.

   entry createWindow (int x0,y0,w,h);
   entry drawWindow();
   entry doThisWithTheWindow (P p) return R;
   entry doThatWithTheWindow (Q q) return S;
end window;

cotask myWindow implements window is

   -- we assume a rule that a nonterminal is implemented on a function with the same name
   procedure stuff(out boolean continue) is
   begin
      select
         accept doThisWithTheWindow(p:P) return R do ... continue := true; end;
      or accept doThatWithTheWindow(q:Q) return S do ... continue := true; end;
      else continue := false;
      end
   end stuff;

begin
   -- an accept swaps context back to the caller and continues on the branch body on the
   -- corr entry
   accept createWindow(int x0,y0,w,h) do ...end;   
   accept drawWindow() do...end;
   boolean continue;
   loop
      stuff(continue);
      exit when not continue;
   end loop;
   accept closeWindow() do...end;
   -- the return here closes the dialog and deletes the context. this is only allowed
   -- on a final node such as closeWindow()
 end myWindow;
         
 procedure myWindowClient() is
 myWindow W;
 begin
    W.create(0,0,1024,768);
    W.draw();
    loop 
       W.doThisWithTheWindow(p);
       W.doThatWithTheWindow(q);
       exit when someTwitThrowsABrickThruTheWindow;
    end loop;
    W.close();
 end myWindowClient;
    

polymorphism is implemented by rewriting one or more accept() branches. a type may implement several such interfaces as a union of disjoint schemas (every interface is considered disjoint), and the base types then become an alternation of nonterminals in the implementor type. a function such as stuff() could also be overridden as a type parametre.

the down side of course is that the system must create a context for the coroutine and in general its not possible to determine an optimal storage size for the context beforehand. a method call also requires a context swap which is slower than a simple function call (and theres an additional type validation overhead on the client end too).

does anyone know of a language that does this? either way your thoughts will be appreciated :)...thx