Predicate Classes

A paragraph from CraigChambers?, "Predicate Classes" (ECOOP ?93 Conference Proceedings)

Predicate classes extend the standard object-oriented modelling constructs by reifying transient states or behavior modes of objects. A predicate class has all the properties of a normal class, including a name, a set of superclasses, a set of methods, and a set of instance variables. Additionally, a predicate class has an associated predicate expression. A predicate class represents the subset of the instances of its superclass(es) that also satisfy the predicate. Whenever an object is an instance of the superclasses of the predicate class, and the predicate expression evaluates to true when invoked on the object, the object will automatically be considered to inherit from the predicate class as well. While the object inherits from a predicate class, it inherits all the methods and instance variables of the predicate class. If the object?s state later changes and the predicate expression no longer evaluates to true, the inheritance of the object will be revised to exclude the predicate class. Predicate classes thus support a form of automatic, dynamic classification of objects, based on their run-time value, state, or other user-defined properties. To the extent that these transient states are important in the application domain, predicate classes can help in modelling and implementing them.

If I understand this, it's kind of like this ...

   self predicate ifFalse: [^super someMethod].
   "continue with method for PredicateClass ..."

In a warped and twisted way, yes.
In effect, yes; it's a way of replacing "if" statements with dynamic method dispatch.

For example, if your base class is a ring-buffer, you can represent the states buffer-full, buffer-empty, buffer-not-full, buffer-not-empty and buffer-partially-full as predicate subclasses (with the obvious inheritance relations). Then you can add code to these subclasses in the usual way. The base class declares a routine that fetches an item from the buffer, which you can override differently in buffer-empty and buffer-not-empty subclasses.

The Smalltalk snippet is a bit misleading because #ifFalse: can only make binary choices. With dynamic method dispatch you can make multi-way choices, one for each subclass. It is also misleading in what it says about the way code is organized. With dynamic method dispatch you keep all the related code together in its class, and separate from unrelated code. These advantages carry over to predicate classes quite naturally. -- DaveHarris

Anyone care to explain this further and maybe give some javaish examples?

No. The explanation so far is more than clear. What isn't clear is:

Who is the aggressor on this page? Same as on LearningFromPrototypes? Does anyone actually consider this material's content to outweigh its presentation, or may I just delete it? --AdamBerger

I wrote it. And the aggression is pretty restrained.

Do you even know what a predicate class is? It's a giant honking case statement in a method that says: That's all it is. Syntactic sugar for a common case of delegation. With the marked disadvantage that it forces you to clearly and consistently express all of the conditions necessary to get into the different states.

With delegation, you can just put a predicate check like "self full ifTrue: [parent* become: FullBuffer]." wherever you want. When your predicates are unclear, complex and inconsistent, delegation is the only way to express them in a simple manner. And when your predicates are clear, simple and consistent, then it's equally simple to put in "self checkPredicates.", or just "checkPredicates." in SelfLanguage, at the beginning of your methods.

If you do this: [parent* become: FullBuffer] then you are mutating state. Predicate classes can work with immutable objects. Also it is possible for an object to be a member of multiple predicate classes simultaneously, which is not possible with a mutable parent. For example, the number 2 is a member of Integer, EvenNumber, PowerOfTwo and PrimeNumber. -- JamesMcCartney

An object can be a member of multiple mutable parent classes at once too. Just have one be a subclass of the others. If they don't form a strait membership relationship, you'll need multiple inheritance. In your example, you'd have the class hierarchy Integer -> PowerOfTwo -> EvenNumber and PrimeNumber, with 2 having both hierarchies as parents. -- JonathanTang

You mean: PowerOfTwo -> EvenNumber -> Integer

The problem is in a language with mutable state, if an object changes state such that it goes from being a member of one class to several classes, you then have to add parent slots in a prototype model. And when it changes state to become a member of fewer predicate classes, you would have to remove parent slots in a prototype model. This management of parents is a huge burden which is not necessary with predicate classes. Parents that represent predicate classes are redundant state and have all the disadvantages of managing redundant state, i.e. if you ever miss a place where that state needs to be synced, then the object has corrupt type. This kind of programming error is not possible with predicate classes. -- JamesMcCartney

And the advantage of doing it this way is that there's no new syntax!

The problem with predicate classes is that they're smack in the middle of an abstraction stack where: I'm sure TableOrientedProgramming fits in there somewhere... I'd say between multiple and predicate dispatch, except that TOP doesn't seem to handle types at all. Whenever the matter is mentioned, topmind just replies that ThereAreNoTypes.

As an abstraction, their usefulness is squeezed between multiple dispatch at the low end and delegation at the high end. Multiple dispatch being less, and delegation far more, general than predicate dispatch.

So just how useful are predicate classes? Nobody knows because predicate classes have never been implemented. And before implementing them, before making the VM send checkPredicates on every message send, before we make checkPredicates an implicit message send at the beginning of every method, why don't we first find out if anyone uses the damn things explicitly?

Until we do, it's best to think of predicate classes as FUD. Especially since their inventor was fully aware of delegation, having worked in Self for years. Especially since there are so many ways to completely fuck up predicate classes in a language that doesn't have delegation. What comes immediately to mind is some idiot inventing special syntax for predicate classes and not reifying predicate classes as real classes. And that justifies the aggression. -- RK

I thought someone had responded to RK already, but I guess not. The syntactic sugar is everything. I find that I often write code that needs to check whether an object has been initialized, whether it's waiting on more data, whether it's written out data, whether it's in a consistent state, etc. Checking these conditions means a large number of guard clauses at the beginning of each method. In well-factored code I've seen, error and sanity checks account for well over 50% of the code. A typical 12-line method might have 8 lines of error checking, and then a 4-line loop that does the actual work.

PredicateClasses let you group all these conditions together in one logical place. If these conditions hold, then that's what the type of the object is. I'd much rather specify a few conditions than try to remember to insert become: calls every time an object's state changes. OnceAndOnlyOnce.

There's an interesting analogy with stack & function call management. There the "abstraction stack" is: Here, by far the most expressive is an explicit stack and jump. You're not constrained to LIFO function order. You can save whatever contents you need. You can make efficiency tradeoffs based on anticipated usage.

I also don't know many programmers who enjoy programming in assembly and managing their own stacks.

Indeed, the most commonly used languages all fall into the first half of the "abstraction stack" (I put that in quotes because it really measures expressiveness, not abstraction). That's because of the well-known dictum of OptimizeForTheCommonCase?. Most programs really don't need the extra power; they do just fine with what they've got. And in the cases where you do (continuations or explicit inspection of the stack), they don't want to go back to handling all the common cases explicitly.

Dispatch is the same way. If people really want to do explicit delegation through if/case statements, they certainly can. But predicate classes cover a "common case" that in my experience has been the source of many bugs. -- JonathanTang

As an aside, it is false to say they have never been implemented: They are quite functional in the little-known system agent simulation system Easel.


There is also Cecil which implements PredicateTypes -- StefanPlantikow?

Of course, if we replaced all of the other forms of dynamic dispatch with delegation--we'd be back to procedural programming. And I don't think anybody considers that a step forward. The advantage(s) of the various forms of ObjectOriented dynamic dispatch - prototype-based or class-based - are as follows:

1. A sometimes overlooked feature of predicative classes is their usefulness for contract-based programming, especially due to the fact that they are semantically stricter than simple interfaces.

2. As has already been said here, another important aspect of predicate classes is that they allow a certain way of cross-cutting the type system (AspectOrientedProgramming) by providing means for dynamically run-time grouping of otherwise unrelated types together (like interfaces do statically). In a statically [typed?] language, this leads to the doubtful possibility of "type-loss".

A very simple approach to this would be to limit state change and aliasing storage of predicatively typed objects, thus allowing the use of dynamic, predicative typing only in a dynamic context. A "javaish example" that just came to my mind:

 class SomethingThatNeedsAQueue
    Queue q1;
    Queue being Empty q2;   // Compiler Error, would allow predicative aliasing

void foo(Queue staying Empty q) { // Implied thread lock around q

Queue being Empty q3;

q1 = q; // ok q3 = q; // ok

q.size; // ok q.enqueue(foo) // RuntimeException if Empty predicate not true after call }

void bar(Queue being Empy becoming Full q) { // Implied thread lock around q

q.enqueue(foo); // ok } }

-- StefanPlantikow?

Why is it even called a "class"? Somebody pointed out in another topic that I can't find again (perhaps related to AttributesInNameSmell or SetsAndPolymorphism) that a truly predicate-based call would look something like:

  do(verb="xxx", noun="xxx", adjective="xx", foo="xx", bar="etc...");

Such is almost ProLog-like. In practice is often difficult to get one's head around such. With a method call or a function call, you know that you are calling either one noun or one verb. The above may result in zero or many segments/snippets/actions being triggered. The MentalIndexability uncertainty is too high for most people unless a new way to teach a change in thinking is found. --top

See also PredicateTypes

View edit of July 27, 2014 or FindPage with title or text search