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