ERights Home elang / guarding 
No Previous Sibling No Next Sibling

Guarding Asynchrony

Stale. Taken from here, but needs rewrite.

The Changing of the Guard

In the thread rooted here, Terry Stanley proposes a set of naming conventions for naming variables, functions, and objects. These conventions have evolved some since then (eg, "Pass" has become "Rcvr"), but we have essentially been following the practices recommended there, with mixed results.

There has been a live controversy raging privately without resolution that we should now make public. Should we be encoding near (supports immediate calls) vs vow (successful resolution supports immediate calls) vs rcvr (just do eventual sends) in the names (as this thread recommends, and as we've been mostly doing since then), or should we instead, or in addition, be using our guard annotations to express these distinctions; as Terry now advocates (thanks!).

Whatever the resolution of this controversy, it seems like a good idea to be able to express these distinctions with guard annotations. This would be especially true if these guards could enforce the property they state, as with other guards. Unfortunately, for the new ":vow" and ":rcvr" guards, we can't enforce these properties at a reasonable cost until we have good support for PassByCopy, so these guards are just advisory for now. Nevertheless, as a way to capture programmer intent in a machine understandable form, this seems worth doing anyway. The relevant guards are:

  • :near - (No change in meaning or implementation.) A near reference passes this guard. All others get rejected. A near reference supports immediate calls on the object it designates. This guard is properly enforced.

  • :pbc - (No change in meaning or implementation.) A near reference to a PassByConstruction object. All PassByCopy objects are also PassByConstruction. When a pbc is passed as an argument, the value as received by the callee will be pbc. An unresolved reference to a pbc will successfully resolve only to a pbc, never to a far reference. This guard is properly enforced.

  • :vow and :vow[valueGuard] - (New as of 0.8.14.) A vow may itself be a near, unresolved, or broken reference. If it's currently unresolved, then its fulfillment (its successful resolution) must be near. In other words, its resolution must be near or broken, but never far. A possibly unresolved reference to a pbc is a vow. A ":vow[valueGuard]" is a vow for something that will pass the argument valueGuard. For example, a ":vow[int]" is a vow for an int.

    This guard is currently not enforced -- it is currently operationally equivalent to ":any" and is used purely for documentation purposes. Once it is enforced, it will send its argument valueGuard to the specimen's host to coerce the specimen there. If the specimen is remote, the valueGuard itself will have to be a pbc object. The ":vow[valueGuard]" will then return a promise for the result of (remotely) coercing the specimen by the valueGuard. This preserves pipelining (which a local check would lose), and prevents any messages from being delivered to a specimen that doesn't pass the valueGuard, but relies of the specimen's host to run the valueGuard honestly. If you want to trade pipelining for local enforcement, this is easy to write in the language, but seemed like the wrong default.

  • :rcvr and :rcvr[valueGuard] - (New as of 0.8.14.) A rcvr is a reference that may be eventual, and whose resolution may be far. Therefore, one should only deal with rcvrs using eventual sends -- never by immediate calls. Without an argument guard, ":rcvr" has the same operational meaning as ":any", and so is properly if vacuously enforced. ":rcvr[valueGuard]" is a reference for an object that will pass the argument valueGuard. As with ":vow[valueGuard]", this is currently unenforced, but will be enforced using the same technique documented above.

  • :any - (No change in meaning or implementation.) The specimen can be any kind of reference, and no coercion is performed (ie, coerce returns its argument). This guard is properly if vacuously enforced. Though there is no operational difference between this and ":rcvr", there is a documentation difference, and a possible difference in static checking rules (see below). Whereas ":rcvr" states that the declarer has special knowledge that he's warning his client of, ":any" is better used when the client may have better knowledge than the declarer. For example, a get/1 from a ConstList would be declared as ":any" rather than ":rcvr", since the declarer know that he's only handing back what was put in, which could be anything; whereas the client knows what he put in.

These annotations seem adequate for a lint-like static checking advisor. Here are some example rules that seem easy to automate. For concreteness, "int" is used as an example of a pbc type, and a guard for such a type that does no coercion (either passes its argument through or complains), and "Counter" is used as an example of a PassByProxy type and corresponding non-coercing guard. In the following "<=" means "subtype" and "=>" means "produces" or "is of type". Note: I am not a type theory kind of guy, so advice and corrections would be especially welcome.

  • near <= vow <= rcvr
  • int <= vow[int]<= rcvr[int]
  • warn that rcvr[int] should be vow[int]
  • Counter <= vow[Counter]<= rcvr[Counter]
  • warn that vow[Counter] may want to be rcvr[Counter], though vow[Counter] can be correct if you know that the counter is necessarily local.
  • Given def f1():int, f1() => int, f1 <- () => vow[int]
  • Given def f2():vow, f2() => vow, f2 <- () => vow (since f2 is a near reference to such a function)
  • Given f2Rcvr :rcvr[F2], f2Rcvr <- () => rcvr (since f2Rcvr may be remote, and a vow to it may be a rcvr to us.)
  • Given f1Rcvr :rcvr[F1], f1Rcvr <- () => vow[int] (since a remote reference to a vow for a pbc is still a vow for a pbc.)
  • when (vow) -> done(near) is ok
  • when (vow[int]) -> done(int) is ok
  • when (rcvr) -> done(rcvr) is ok
  • Given def f3(int), f3(int) is ok, f3 <- (int) is ok
  • Given def f4(counter), f4(counter) is ok, f4 <- (counter) is ok (since both f3 and counter are near references.)
  • Given f4Rcvr :rcvr[F4], f4Rcvr <- (counter) is bad (since f4Rcvr may be remote.)
  • Given counter :Counter, warn of f4Rcvr <- (counter), since it's probably bad, but is sometimes necessary and appropriate if the programmer has special knowledge these are both resolved references into the same remote vat. I doubt we should try to capture such special knowledge in static checking rules.

This list is intended to be more suggestive than correct or complete, in the hopes that someone who has competency with such matters may take it from here. Please let me know if you're interested, or start an argument about these rules on the e-lang list.

Unless stated otherwise, all text on this page which is either unattributed or by Mark S. Miller is hereby placed in the public domain.
ERights Home elang / guarding 
No Previous Sibling No Next Sibling
Download    FAQ    API    Mail Archive    Donate

report bug (including invalid html)

Golden Key Campaign Blue Ribbon Campaign