Split Capabilities: Making Capabilities Scale

Dan Bornstein danfuzz@milk.com
Tue, 25 Jul 2000 15:32:54 -0700 (PDT)


[Apologies in advance: I wrote this note in bits and pieces during a
somewhat busy workday. I hope it's at least somewhat coherent.]

MarkM writes:
>Were this done is a Java-like statically typed language, both increment
>and decrement facets would be seen as being of type
>
>     public interface {
>         void do();
>     }
>
>with no further type information available.  

Alan Karp writes:
>Seems like an odd programming model where I say "do your thing whatever
>that may be". What would Werner von Braun say during a countdown, do() or
>decrement()? What would he say if he saw the count increase as he tried to
>count down?

I wrote:
>The thing to keep in mind is that, in any useful system, this object would
>have been received in an already-rich context. In von Braun's case,
>presumably he got it by calling the getCountdownDecrementer() method on a
>particular missileControl object, the same object he uses as the target of
>repeated calls to getCountdownValue(). If he saw the count go up, he might
>choose to call missileControl.abort() and then do further research after
>things had settled down.

Alan Karp writes:
>My point exactly.  Had he known that the object supports increment, he'd
>know that the countdown was on hold.  Seeing only the facet and thinking
>it's an object, he can only assume there is an error that forces him to
>abort the launch.  Strict object encapsulation says that the object state
>can only be changed through the interface.  Since he sees no increment
>method, he must assume that the object has a flaw.

It seems that we're seeing the same situation and drawing opposite
conclusions. 

My point is that if the object is in fact a "facet" of a system that
supports increment, von Braun *would indeed* know that because he got the
object in a context where he knows what all is available. And if the
missileControl semantics included having a launch be "on hold" then von
Braun would also know about that, and he would have been coded as
appropriate for that situation.

The thing about checking the counter was me attempting to describe a
possible sanity check or assert. I was starting from the assumption that
the missileControl had the three methods (get countdown, decrement
countdown, and increment countdown). von Braun *knows* the counter isn't
supposed to increment during a countdown, but just to be safe he checks his
assertion using the provided facility. If there's a problem, he aborts the
mission. If the count increment weren't available at all in the first
place, then this assertion check would probably be unnecessary. And, if
that were the case, von Braun would know, from context, that it indeed
wasn't a part of the missileControl interface.

I wrote:
>The way out of the mess is to assume that one is starting from a context
>of objects that one knows certain properties of, such as whether they are
>trustworthy. Assuming von Braun has reason to trust the missileControl
>object in question, then he would transitively trust the result from
>calling missileControl.getCountdownDecrementer(), whether or not he's
>using a strongly-typed class-based system.

Alan Karp writes:
>This reasoning is exactly why I don't feel comfortable treating a facet as
>an object. The facet hides part of the interface and tells me only some of
>the properties.

I don't get your reasoning. Yes, the facet hides part of the interface and
tells you only some of the properties. But what's wrong with that? (Even
stronger: that's the point exactly!) One never really has an object in
isolation. Or, if one only has an object in isolation, there's a reason one
got to be in that situation.

Let's look inside von Braun's programming (extreme psuedocode and code
personification alert). First, I'll assume that missileControl in fact has
these methods:

    object missileControl {
        fun :any counterDecrementer();
        fun :any counterIncrementer();
        fun :any counterGetter();
        fun :any aborter();
        fun :any launcher();
    }

All of these methods are meant to return a(n anonymous) function to do the
job the name implies (e.g., the result of calling aborter() is a function
that, when called, aborts the launch).

Let's say von Braun himself is just responsible for performing the
countdown and signalling either to launch or abort, and he is handed a
missileControl object to do his work. von Braun assumes that he isn't the
only object with access to the missileControl and wants to faithfully send
an abort signal if he notices that anything is amiss (in particular, if the
counter ever increases). von Braun might be written as this:

    # von Braun works by spawining a launchControl process and
    # a countDown process.
    fun vonBraun (missileControl) {
        spawn launchControl(missileControl.counterGetter(),
                            missileControl.launcher(),
                            missileControl.aborter());
        spawn countDown(missileControl.counterDecrementer(),
                        missileControl.counterGetter(),
                        missileControl.aborter());
    }

    # launchControl can get a count, issue a launch, and issue an abort.
    # it repeatedly gets the count and issues a launch if the count is
    # zero, or issues an abort if the count ever increases. it exits after
    # sending either a launch or abort signal.
    fun launchControl (counterGetter, launcher, aborter) {
        var oldCount = counterGetter();
        repeat forever {
            var newCount = counterGetter();
            if (newCount > oldCount) {
                aborter();
                break;
            }
            if (newCount == 0) {
                launcher();
                break;
            }
            oldCount = newCount;
        }
    }

    # countDown can decrement a count, get a count, and issue an abort.
    # it issues one decrement per second and checks to see if the count
    # is as expected. if not, it issues an abort. it exits when the count
    # is zero or immediately after sending an abort signal.
    fun countDown (counterDecrementer, counterGetter, aborter) {
        var oldCount = counterGetter();
        repeat forever {
            sleep 1 second;
            counterDecrementer();
            var newCount = counterGetter();
            if (newCount != (oldCount - 1)) {
                aborter();
                break;
            }
            if (newCount == 0) break;
            oldCount = newCount;
        }
    }

So, what von Braun has done is broken the problem into two pieces. Neither
piece requires the full missileControl capability, and so he doesn't pass
that to the sub-processes. He only passes them the capabilities they need,
which in this case are all facets of the same missileControl object. This
makes it easier for him to verify the correctness of his subprocesses.
Also, since he's the one that instantiates the subprocesses, he knows that
the only capabilities they have are the ones he gave them, and so they
*cannot* misbehave by using the counterIncrementer(). Furthermore, even
though von Braun himself can send a launch(), he knows that of the two
subprocesses, only launchControl() may do that; countDown() can't because
he didn't pass it the capability to do so.

By the same token, the thing that instantiated von Braun himself (the
government?) knows that, since von Braun was only given a particular
missileControl object (and nothing more), he also cannot misbehave in
certain ways. For example, it isn't necessary to check that whether von
Braun is taking money from Fort Knox, since he was never passed the
fortKnox capability. However, since they chose to give him the full
missileControl capability, it might be worth their while to do additional
sanity checks on his actions, just in case, during code review, they missed
a place where he calls or leaks a reference to
missileControl.counterIncrementer().

None of the above requires that any of the objects themselves be labelled
explicitly with "what they are." von Braun, by his nature, trusts the
missileControl he is given. The aborter that von Braun passes to his
subprocesses is an aborter merely because it was the result of calling
missileControl.aborter() on a missileControl object that von Braun trusts.
The aborter that the launchControl subprocess receives is trusted
implicitly, because the basis of launchControl's operation is in receiving
three trustworthy objects that interact in a particularly well-defined way.
Type annotations might help debug the system, but having code that passed
type inspection is not an assurance of its correctness.

-dan