So, I have been thinking some more about my design and been reading some interesting papers the nice people at UCI gave me when I visited the other day to seed my literature review. I realized, there is a huge issue which I have overlooked so far: delay and asynchronicity.
On a side note, it seems that neither links nor Luna concern themselves much with this particular problem, but it has been brought to the attention of one of the developers of Links after a talk he recently gave, cf. here.
I realized that in a system where the compiler decides which object lives where (client or server) maybe even the VM at runtime, any function call could potentially cross the wire. There is just no way to know. Now the problem arises when something goes wrong or a function call takes a little bit longer which can potentially happen in any function call, as already mentioned. Since the whole goal is to abstract/hide away as much of that knowledge as possible from the programmer – shield him from having to know and think about that, if you will – that is a problem.
This is because what should be done when that happens is heavily dependent on where it occurs and cannot be decided by the compiler or even by the VM at runtime. In traditional RPC programming this has to be taken care of by the programmer explicitly, because all remote calls are marked asynchronous and may fail for any reason. This gives him the opportunity to take the appropriate action, like displaying a loading icon for a delayed call.
One way to mitigate this problem is to employ caching to survive intermediate connection failures, however this introduces a lot of overhead to keep caches in sync. Now we face a dilemma in that we want to shield the programmer from having to know about the partition as much as possible, without impeding his ability to appropriately react to errors or delays. A couple of ways to deal with this come to mind:
- Don’t deal with it: Very bad idea, errors will be thrown left and right and in case of delay the user has no idea whats going on.
- Change all call semantics to asynchronous: this works, but will incur a lot of overheard and most importantly the transparency of the partition is completely gone, since the programmer explicitly has to deal with every function call being asynchronous now.
The best solution in my opinion: use caching to reduce the problem slightly. Use traditional exceptions to deal with failures since they offer the right semantics: Program flow is interrupted and they filter up the call stack until they are caught and handled, giving whoever started the execution of this particular flow a chance to deal with it.
As for asynchronicity, I think functions should retain their original semantics: block on a delayed call. The compiler can introduce code for a default response in case this happens, like showing any of the familiar spinning ajax loading indicators. But the programmer needs to be given the opportunity to detect and deal with it in a manner appropriate to the specific situation. Exceptions do not offer the right semantics for this situation since they break the program flow. I think this is something truly crosscutting, more akin to advices in aspect oriented programming.
So my suggestion is: have advices that act like events which can be as granular as needed by the programmer – down to single function calls, classes or complete packages. When an event gets triggered by a delayed function call it searches back through the call stack until it finds the innermost function, class or package that has a handler. If none is found the default compiler-introduced handler at the top of the stack is called.
This way the programmer has every possibility to let the user know what is happening. Sadly, this also means that some of the transparency is gone, though.