Walnut/Distributed Computing/Testing for Equality in a Distributed System

Testing for Equality in a Distributed System
As noted above, you can send eventual messages to both promises and far references. So if you plan to treat the fulfilled object as a far reference, using only eventual sends, there is usually no reason to use a when-catch to wait for resolution. Just go ahead and start sending messages, uncaring of whether the promise has yet been fulfilled.

There are four reasons to use a when-catch construct to resolve the promise for a far object.


 * To find out that the original request succeeded, or otherwise, to get the problem
 * To flush out all previous messages sent via a reference to an object before taking an action
 * To use the result as a key in a hash table (a promise cannot be used as a hash key)
 * To test for equality, i.e., to see whether the promised object is the same object that you received from another activity.

Here is a test for equality:

def polePositionCarRcvr := raceTrack <- getPolePositionCar when (polePositionCarRcvr) -> { if (polePositionCarRcvr == myCar) { println("My car is in pole position") } } catch prob {}
 * 1) E syntax

Note that the catch clause is empty. Because remote references across a network are unreliable, you will usually want to handle the problem that triggered the catch clause. However, if you have many pending actions with a single remote object, all the catch clauses for all those actions will start up if the connection fails, so you may want to disregard most of the catch clauses and focus on handling the larger problem (loss of connection) in one place. Often in this situation you will want to set up a whenBroken message, described later.

But in this example, there is no compelling special action to take if you can't find out whether your car is in pole position, so no action is taken.

Making your own Promises
If you write a function or method that must get values from a far object before returning an answer, your function should return a promise for the answer, and fulfill that answer later. You can create your own promises using Ref.promise in E :

def calcMilesBeforeEmptyVow(carRcvr) { def [milesBeforeEmptyPromise, milesBeforeEmptyResolver] := Ref.promise def fuelRemainingVow := carRcvr <- getFuelRemaining def mpgVow := carRcvr <- getEfficiency when (fuelRemainingVow, mpgVow) -> { milesBeforeEmptyResolver.resolve(mpgVow * fuelRemainingVow) } catch prob { milesBeforeEmptyResolver.smash(`Car Lost: $prob`) }    return milesBeforeEmptyPromise } def myCar { to getFuelRemaining {return 5} to getEfficiency {return 2} } def milesVow := calcMilesBeforeEmptyVow(myCar) when (milesVow) -> { println(`miles before empty = $milesVow`) } catch e {println(`Error: $e`)}
 * 1) E sample
 * 1) ....Meanwhile, somewhere else in the program....

This example highlights several different features of promises. The basic idea of the example is that the function calcMilesBeforeEmptyVow(carRcvr) will eventually compute the number of miles the car can still travel before it is out of fuel; later in the program, when this computation is resolved, the program will print the value. However, before we can do the computation, we must first get both the fuelRemaining and the milesPerGallon values from the remote car.

The promise and the resolver for that promise are created as a pair using Ref.promise. They are "normal" variables in the sense that they can be passed as arguments, returned from methods as answers, and sent eventual messages. Resolvers truly are ordinary objects. There are 3 restrictions on the promises: they cannot accept immediate calls, they cannot be tested for equality, and they cannot be used as keys in hash tables.

In this example, the function returns the milesBeforeEmptyPromise to the caller just as it would return any other kind of value. To cause the promise to resolve, the function calls the resolver (or sends to the resolver if the resolver may be remote to your program) with the "resolve(value)" method. To break the promise (which produces a problem that will cause the catch clause of a when-catch to execute), call the resolver with the "smash(problem)" method.

It is possible to chain resolutions: you can resolve a promise by handing the resolver another promise (though if you hand the resolver the promise which it is intended to resolve, E will detect the problem and automatically break the promise--a promise resolved to itself can never be fulfilled). If you resolve a promise with another promise, the original promise is not yet considered resolved, i.e., the when-catch body will not be activated by such a resolution. The when-catch body will only be activated when the entire promise chain is resolved, and there is an actual reference to an actual object available.