User Tools

Site Tools


why_isn_t_it_dci_if_you_use_a_wrapper_object_to_represent_the_role

This is an old revision of the document!


Why isn't it DCI if you use a wrapper object to represent the Role?

Explaining it kind of spoils the fun… There are several ways to explain this. Let me try this way. I'm winging it, and this won't pass for a published paper. And it's a difficult topic, and even more difficult to explain it to someone who doesn't already know it. I'll do my best.

Ruby convention embraces, among others, the following binary operators (see http://kentreis.wordpress.com/2007/02/08/identity-and-equality-in-ruby-and-smalltalk/):

    puts "Object 1 is == to object 2: #{object1 == object2}"
    puts "Object 1 is eql? to object 2: #{object1.eql? object2}"
    puts "Object 1 is equal? to object 2: #{object1.equal? object2}"

Let me emphasize that these are all accessible to application programmers (i.e. they aren't just for platform, framework or compiler people), and that they mean different things. Usually == designates value equality. In Ruby it can be redefined. That means that even with a wrapper implementation you can fool clients into believing that two different objects are “equal.” The only exception here is .equal?, which you can't redefine without totally redefining what it means to be an object. (More on this below but, briefly, it's the identity operator and, by definition, an object has state, identity and behavior.)

The .eql? operator is similar, except it returns an indication according to whether its operands have the same type and also obey ==. So it is true that 1 .eql? 1. It is not true that 1 .eql (1.0).

The .equal? operator is the identity operator. If I say a = 1 and b = 1 then it is not true that a .equal? b.

So let's say that you write an algorithm like Dijkstra's algorithm and you want to know if a give object is in the set of unvisited objects. Consider this code:

    unvisited.each_key {
        |intersection|
        if unvisited[intersection]
            if intersection.tentative_distance < min
                min = intersection.tentative_distance
                selection = intersection
            end
        end
    }

The question is: How does unvisited[intersection] work? It's a hash lookup. It turns out that hash uses == (think of it as a loop going through an associative data structure doing comparison on the keys.)

So we want a generic Dijkstra's algorithm that can work on any generic type of node. (Remember that DCI allows us to substitute many kinds of object to play a given role, as long as they follow the role contract). Let's say that the nodes in my example represent the altitude above sea level, so when I invoke == on them, any two nodes at the same altitude would compare equal. I have the right to override the behavior of == in that way. My universe of nodes can comprise several equivalence sets, with the number of sets being bounded by the number of nodes. The point is that == does return true when comparing a node to itself, but can also return true if one node is compared to an equally elevated “alias.” That would cause the above example to fail.

The problem here is that identity, shallow value, and deep value are three different binary relations. Identity is fundamental to the object model: if you violate identity, you break the object paradigm (an object has state, identity, and behavior). Object orientation itself says nothing about the intrinsic meaning of value comparison, which means that the programmer has the freedom to redefine it at will.

And in my Dijkstra example, I have done exactly that.

A fundamental problem arises in wrapped implementations. Let's say that I wrap domain class instance O with role wrapper south_neighbor. At some point the same object is wrapped with the role west_neighbor in another iteration (or recursion) of Dijkstra's algorithm. Which of these is true?

    west_neighbor == south_neighbor
    west_neighbor .eql? south_neighbor
    west_neighbor .equal? south_neighbor

It depends. The wrapping people say you can “fake out” the == case because, after all, the object paradigm doesn't stipulate what value comparison means. But you can't fake out .equal? without violating object identity. In theory a wrapper could re-define .equal? but it's a real mess. What if the wrapped object were wrapped again? How many levels deep of fake-out does one need? How does one know whether to propagate the fake-out or to take the pointer directly? And how about other parts of application code that honestly depend on on .equal? meaning “object identity” for real?

The Dijkstra example is a no-win scimitar for wrapper aficionados. That'w why, with wrappers, you can only *almost* makes it work. I hope you don't work for a company with the goal of delivering software to me that *almost* works… That's where the wrapper folks belong.

This was a nightmarish bug in the development of DCI. There is no way Trygve could get it to work in Smalltalk, particularly with return values from methods. It was hell to find the problem.

Some guys published a paper at SPLASH last year proving that you could hide all of this distinction behind a wrapper. I disproved the paper in a single program, and the record has been set straight in this year's SPLASH proceedings, demonstrating that the earlier paper was flawed.

This, at some level, lies at the core of what object-oriented programming is about. OOP has a different virtual machine model than with procedural programming. In C, variables are memory locations: there is no way to separate an identifier from an address. We simulate this in C using pointers, but the pointer itself is an object that, in a given scope instantiation, is indelibly bound to some memory location. In OOP identifiers and objects are two different things. True OO languages drive their memory management model from this fact: when the last label is unpeeled from an object, then the object is not reachable from anywhere in the program, and its memory may be reclaimed. This is sometimes called garbage collection.

In C object lifetimes (extent) follow scopes. In true OOP object extent follows its binding to identifiers. Those bindings are independent of scope.

Of course, classes are scopes, so a class-oriented programmer starts thinking in terms of class scopes, method scopes, nested class scopes, and all kinds of other garbage that, in the end, isn't real. In a program at run time there is one kind of scope: an object. It is one level deep.

This flies in the face of class-oriented programmers, but it's equivalent to the underpinnings that distinguish full object-oriented programming from what you know from class-oriented, statically typed, scope-structured languages.

To not understand the fundamental failure of wrappers is to have only a shallow understanding of object-oriented programming. (And you have this straight from the guy who popularized PIMPLs (http://c2.com/cgi/wiki?PimplIdiom).)

why_isn_t_it_dci_if_you_use_a_wrapper_object_to_represent_the_role.1389739200.txt.gz · Last modified: 2014/01/14 22:40 by jcoplien