Slots-based object models aren't object-oriented

Computer

Most dynamic object-oriented programming languages in use today are slots-based. These include Python, PHP and Javascript.

The slots-based model is based on the idea that an object is a collection of selectors that map to members. The members are either references to other objects, or some kind of function object representing a method. When another object invokes a method, it does so by searching the selector map for a matching selector, then invokes the function (if any) that is retrieved from the map.

Some nice things about the slots-based model are:

  • Slots can be used to represent all state, such as instance variables.
  • Slots make it easy to modify or add methods dynamically by simply assigning a new function to a given selector in the map.
  • Reflecting upon an object yields a standard collection (usually a map) that can be dealt with as a first class object.
  • It is possible to pull out a function from an object and deal with it as a first class object, storing and invoking it later, for example.

Kew follows a different model: its objects are dispatcher-based. This paradigm used to be more common but seems to be on the wane (with some caveats you could argue that it is the paradigm employed by Smalltalk, for example).

In the dispatcher-based model, an object is a function that dispatches and executes method invocations. It is not possible for the caller to inspect the map from which this is done, nor to verify that a map is being used for dispatch. It is also not possible to modify the map (changing behaviour dynamically) or squirrel away a function for later invocation.

If the dispatch function can’t be modified by a caller (in Kew it can’t), a number of things are not possible:

  • The dispatch function cannot have state added to it. This is good because implementation decisions like whether an object is stateful belong to that object, not the caller.
  • The dispatch function cannot have its behaviour changed by a caller. This is good too because it means other callers can rely on consistency in the semantics of an object’s methods. This is very important in Kew because security is designed in, and you can’t easily get security if people can change the methods on the SecurityManager, for example. Changing the implementation of an object is another form of encapsulation-breaking.
  • It is not possible to reflect on the dispatch function as a first class object. Nor is it necessary, because it already is a first class object. If a caller wants to invoke a method then it can just go ahead and invoke it. It will be handled by the dispatch function somehow—perhaps by signalling an exception. To do otherwise is a violation of the “tell, don’t ask” principle and breaks encapsulation.

    Common patterns of use such as creating proxies to forward invocations to remote objects do not require encapsulation to be broken; just send the message on and return any results or exceptions. The remote object will decide what it can handle.

    Another common pattern is to reflect upon method selectors (e.g. JUnit invokes all methods whose names start with “test”). This is not necessarily a good pattern (an alternative is just to list out the things you want run), but it’s easy to achieve in Kew using Source of MyObject to return an object model of the source code (although this does not allow a malicious caller to obtain references to objects that break encapsulation, it can allow a caller to obtain secrets, such as a hard-coded password string, and so is subject to security checks in Kew).

  • It isn’t possible to pull out a function of an object to store and invoke it later, and again it isn’t necessary to do so. In a slots-based language it’s just as valid to create a simple object that encapsulates the object and method to be called and store and invoke that. It should be indistinguishable from the underlying method function and yet does not break encapsulation. I argue this is better design, because you should create new behaviours by creating new objects, not by taking objects to pieces (although many languages make this less convenient than just reaching in and grabbing the underlying method function).

So, to summarize, the slots-based object model isn’t really object-oriented because it’s centered on the methods of an object, not on the object itself. It allows callers to break encapsulation, and exposes them to implementation details.

Many people do use slots-based languages to program cleanly and effectively and it’s telling that you don’t see the kinds of activities described as “advantages” of the slots-based model happening much in most people’s code. While it can be seen as powerful, all the advantages can be gained in other, better ways, and the usual disadvantages of breaking encapsulation avoided.