I actually have this problem now in flx so I thought I'd solve it
using fibres instead of callbacks. Here's a comparison
of a simplified example (not tested!).

//===================================================
// Problem: Messages type A processed by routine A.
// Add type B, routine B.
// Errors for none of the above, type E.

union msg = mA | mB | mE;

//===================================================
// Solution 1: callbacks/subroutines
//
// Concept: process what we can, if we can't
// pass the buck.

// Separated components
proc A (nxt: msg->0) (m:msg) {
 match m with
 | mA => println "A";
 | ?x => nxt x;
 endmatch;
}

proc B (nxt: msg->0) (m:msg) {
 match m with
 | mB => println "B";
 | ?x => nxt x;
 endmatch;
}

proc E (m:msg) {
 match m with
  | mE => println "Error";
  | => assert false;
  endmatch;
}


// Integrator
fun processor () : msg -> 0 = {
  var clsB = B E;
  var clsA = A clsB;
  return clsA;
}

// Controller
proc controller (msgs: list[msg]) {
  var p = processor ();
  for m in msgs do p m; done
}

//=========================================================
// Solution 2: fibres/coroutines
//
// Concept: process what we can, if we can't
// pass the buck.

proc fA (nxt: oschannel[msg]) (m:ischannel[msg]) () {
  while true do
    match read m with
    | mA => println "A";
    | ?x => write nxt,m;
    endmatch;
  done
}

proc fB (nxt: oschannel[msg]) (m:ischannel[msg]) () {
  while true do
    match read m with
    | mB => println "B";
    | ?x => write nxt,m;
    endmatch;
  done
}

proc fE (m:ischannel[msg]) () {
  while true do
    match read m with
    | mE => println "Error";
    | _ => assert false;
    endmatch;
  done
}

// Integrator
proc fprocessor (o: &oschannel[msg]) {
  var iE,oE = mk_ioschannel_pair[msg];
  var pE = fE iE;
  var iB,oB = mk_ioschannel_pair[msg];
  var pB = fB oE iB;
  var iA,oA = mk_ioschannel_paior[msg];
  var pA = fA oB,iA;
  spawn_fthread pE;
  spawn_fthread pB;
  spawn_fthread pA;
  o <- oA;
}

// Controller
proc fcontroller (msgs: list[msg]) {
  var o : oschannel[msg];
  fprocessor (&o);
  for m in msgs do write$ o,m; done
}
//==============================================================

As you can see the fibre solution is a tad longer.
So what's the advantage?

In this simplified case there is none!

The callback based solution is perfectly good... 
.. or is it?

No, it isn't. Its nowhere near as good. Here's why:

SPECIFICATIONS CHANGE.

Suppose now our processing of a message
depended on the last one, in other words
there's an ordering dependence. This is true in 
the real case I'm dealing with (namely,
if you set FLX_TARGET_SUBDIR you have to have
previously set FLX_INSTALL_DIR because its a subdirectory
of the install directory).

Now we need state to do it right. The callback solution
as written has nowhere to put the state! 

Of source we can fix this:

fun make_callback () {
  var state: state_t;
  proc callback (m:msg) { .. if state == ... }
  return callback;
}

However, now we have to write
a switch on the state, to handle the message correctly, since the action
is state dependent.

On the other hand with the fibre we could just add the state variable
as a local variable. But actually there's a better way:

USE THE PROGRAM COUNTER AS THE STATE VARIABLE.

That way we need no explicit variable, no type for it, and no switch!

Note: this is, in fact, equivalent to the functional programming method
of continuation passing (because the program counter IS the continuation).

Consider for example message types A1 and A2:

proc fA (nxt: oschannel[msg]) (m:ischannel[msg]) () {
A1:>> 
   match read m with
    | mA1 => println "A1"; goto A2;
    | ?x => write nxt,m; goto A1;
    endmatch;
A2:>>
   match read m with
    | mA2 => println "A2"; goto A1;
    | ?x => write nxt,m; goto A1;
    endmatch;
}

Notice out of order message are passed on in this particular
solution and will be trigger an assert false in fE.

Of course this is a finite state machine with two states, and the
callback based solution would be similar except:

1) Instead of using the "stack and program counter" to maintain
state, it just uses a variable, external to the callback.

2) It has to put the variable INTO the program counter with a switch
to go to the right handler code.

Although this is not too onerous with a finite state machine with two
states .. if your logic is a push down automata with a more complex
state you will end up EMULATING recursion and a stack.

With the fibre solution.. well the implementation is the same.
The difference is Felix does the emulation for you.
It actually generates callbacks with a switch.

In really complex situations you need manually maintained
state variables, however the Felix mechanism reduces the
amount of state you have to manage (to whatever you cannot
fit into a notional stack/program, counter).

Only .. even that is not true. Why? Because, when it gets complex
you can always add an extra stack/program counter. How?

Well you launch another fibre of course!!

--
john skaller
skal...@users.sourceforge.net
http://felix-lang.org




------------------------------------------------------------------------------
Slashdot TV.  
Video for Nerds.  Stuff that matters.
http://tv.slashdot.org/
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to