Multiple dispatch and Symmetric methods - Where Does That Code Live?

Hi all.

I am stuck with the above problem, which looks to be a very common and fundamental one. Supposing I have some sort of relation or operation to be performed on various different pairs (or n-tuples) of classes of object. Where does this code belong, given that it is not sole property of any one of the classes, but a kind of property of the collection of classes?

An bad example for me to bring up, but one that neatly illustrates the point, is the equals method. In order to retain the symmetric and transitive properties of the equals method, and object can only make judgement calls about it being equal to another object of it's own type. This prevents the concept of equivalency from crossing between types.

In order to compare a with b (if a and b are of different types), b must supply some sort of representation of itself as an a, then the comparison statement a.equals(b.asA()); must be called.

This of course means b must supply an 'asXXX()' method for each class XXX it wishes to equate itself to. Furthermore, by an extension of the equals contract for symmetricality, the method AsB() in class A (if it exists) must be such that, if a.AsB() .equals (b), then b.AsA.equals( a ).

This sort of design is unfeasible for obvious reasons, the main reason being that it is impossible to anticipate evey case where an instance of class A could reasonably be compared to an instance of some other class X.

The other annoyance is all that hard work of providing methods to equate A with something else, would go unused for 99% of the time.

With this in mind, I was thinking. Suppose in some program environment, we have only 3 classes, A, B, and C, such that:

{A,B} can be meaningfully compared

{B, C} and {A, C} cannot.

It would be OK to supply code (somewhere) for comparing A's with B's. We also know that under no circumstances will an A or a B, and a C, ever need to be compared for equality.

Supposing an extension of this environment were created, introducing class D.

{D, A} and {D, B} can be meaningfully compared.

Now, neither A nor C knows what a D is, or how to compare themselves to it. But D was developed with A, B and C in mind, and contains logic for the various comparisons (with A and with B). An obvious idea for this is to let D bring it's new code into play, byregistering these new comparison functions in the environment somehow.

Supposing instead that equality comparison was delegated to some third party, instead. This third party contains a table of pairs of classes, which act as keys to look up a comparison object.

So, when class A is loaded, something of the sort happens:

publicclass A{

static{

Equals.comparer.addEntry(A.class, B.class,new EqualsMethod(){

// details of the EqualsMethod interface implementation go here

});

}

}

publicclass B{

static{

// since equals is symmetric, this method clashes with the above in A

// This could happen...

Equals.comparer.addEntry(B.class, A.class,new EqualsMethod(){

// ... different approach to the above

});

}

}

publicclass D{

static{

Equals.comparer.addEntry(D.class, A.class,new EqualsMethod(){

// ...

});

Equals.comparer.addEntry(D.class, B.class,new EqualsMethod(){

// ...

});

}// Ds can now be compared with Bs and As. They can now be compared with Ds

}

There is a problem with the above. As can clearly be seen, there are 3 unique situations that might occur between two classes (call them X and Y).

o One of X or Y contains code to compare them both.

o Neither X nor Y contain code to compare the two. X equals Y is always false

oBoth X and Y contain code to compare the two.

The third causes the problem. What if X and Y disagree on how to compare themselves? Which method gets chosen? The only solution would be to let whosever static initialiser gets called first be the one to supply the code.

I said before that equals was a bad example to bring up. this is because the usage of equals and the concept of equality in java is already well defined, and works just fine. However, in my own pet project at the moment, I have run into the same problems as outlined above.

I am currently assembling a generic pattern API for use in various other applications I am writing (I was finding that I was writing code for matching objects to patterns, in different guises, quite frequently, so I made the decision to refactor it into its own package). An important part of the API is the section that handles the logic for combining patterns, ie with AND, OR and NOT operations.

The Pattern interface is this:

interface Pattern<E>{

publicboolean match(E toMatch);

public Pattern<E> not();

public Pattern<E> or(Pattern<E> other);

public Pattern<E> and(Pattern<E> other);

}

There are a few basic Patterns:

TruePattern<E> - a pattern that always returns true no matter what E is passed for it toMatch

FalsePattern<E> - self-explanatory.

ExactValuePattern<E> - true if and only if the E that it is passed toMatch is .equal to the E that this pattern was constructed with.

NotPattern<E> - a pattern that contains another pattern, and returns true for any E that returns does not match it's contained pattern. Used for when the contained pattern cannot be logically reduced to one pattern under the NOT method in the Pattern interface

AndPattern<E> - a pattern that contains 2 other patterns, and returns true for some E iff both contained patterns return true for that E. Used for when the 2 patterns cannot be logically reduced into one pattern via the AND method in the Pattern interface

OrPattern<E> - self explanatory

RangePattern<E extends Comparable ><E>> - a pattern for comparing if some Comparable lies between two other comparables.

Every pattern has the opportunity to provide a reduction, when combined with another pattern through And or Or. For example, any pattern AND a FalsePattern can be reduced just the FalsePattern. any pattern OR a TruePattern can be reduced to just the TruePattern.

The methods AND and OR from the Pattern interface present the same problems as the .equals() example I was using before.

o AND and OR are symmetric operations

o There exist situations where two patterns of unrelated class could be meaningfully combined (and reduced) under these two operations.

Example: The pattern on Integers '0 < X < 3' and 'X is 5' can be reduce to the FalsePattern

Example: The pattern on Doubles '0 < X <= 10' or 'X is 5.5' or 'X is 7.2' can be reduced to '0 < X <= 10'.

Example: The pattern on Integers ('0 <= X <= 5' and 'X is not 0') or ('X is 6') or ('x is 7') can be reduced to '1<=X<=7'.

So the question is, where does the code belong? a.and(b) should return the same as b.and(a), but both b and a may well disagree as to what happens when they are combined under and. At present, both a and b need to supply their own separate implementations. Clearly though, the code for combining patterns A and B belongs to neither A alone, not B alone, but to A and B as a whole.

Thanks in advance, and sorry for this overlong post.

[8984 byte] By [fragorla] at [2007-10-2 15:28:29]
# 1

> The third causes the problem. What if X and Y

> disagree on how to compare themselves? Which method

> gets chosen? The only solution would be to let

> whosever static initialiser gets called first be the

> one to supply the code.

>

> I said before that equals was a bad example to bring

> up. this is because the usage of equals and the

> concept of equality in java is already well defined,

> and works just fine. However, in my own pet project

> at the moment, I have run into the same problems as

> outlined above.

There's a simple answer to this. You have to think outside the box... I mean Object. Take two comparables. They suffer from the same issue that you describe above for equals. Now consider the Comparator interface. The problem disappears.

I liken this issue to two men of similar height trying to determine who is taller just by looking at each other. They can't do it reliably. They need a reference that provides the proper perspective.

I see the real problem here being a blind adherence to dogma. The solution is simple, effective, and relaible yet it is rejected by many because it's considered to be 'non-OO' (something I disagree with.)

dubwaia at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...
# 2

Thanks for the reply, dubwai, and taking the time to read through that unnecessarily long post ;)

So the equivalent thing in my scenario would be an AndAnator, and an OrAnator? :)

The thing is, it would be nice for comparison between A and B to take place automatically, without the poor coder having to maintain a reference to a Comparator and invoke the comparison method each time. Likewise in my situation, it'd be nice to say A.or(B) instead of andAnator.and(A,B), yet have all the goodness of a third party doing the comparison (or in this case, the anding).

I am going to go and test this all out, but do you have any suggestions on a good structure? I think it would involve pulling the and/or/not methods from the interface (this appeals, as many implementors may not wish to supply logic for running these methods anyhow), and then putting them... somewhere else.

fragorla at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...
# 3

> So the equivalent thing in my scenario would be an

> AndAnator, and an OrAnator? :)

>

> The thing is, it would be nice for comparison between

> A and B to take place automatically, without the poor

> coder having to maintain a reference to a Comparator

> and invoke the comparison method each time. Likewise

> in my situation, it'd be nice to say A.or(B) instead

> of andAnator.and(A,B), yet have all the goodness of a

> third party doing the comparison (or in this case,

> the anding).

>

> I am going to go and test this all out, but do you

> have any suggestions on a good structure? I think it

> would involve pulling the and/or/not methods from the

> interface (this appeals, as many implementors may not

> wish to supply logic for running these methods

> anyhow), and then putting them... somewhere else.

I didn't consider your speicifc problem very deeply because after such a long detailed explanation I figured you'd be up for kicking around the idea for a while.

In your case, I see that you would want to be able to call the and and or methods on the Objects themselves. Luckily, in this case, I think you can have your cake and eat it too.

You can make your and and or methods fa鏰des to the third party 'referee'. This way you can enfore symmetry. It's difficult (if not impossible) to enoforce transitivity with this design but I think you don't even need that in this case. That is if a == b and b == c then a == c should be true but if a and b and b and c then a and c is not necessarily true. In your case, it may not even make sense.

dubwaia at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...
# 4
marker interfaces?
mchan0a at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...
# 5

Yes, I am definitely up for kicking this idea around some.

I could indeed make the and and or methods facades to the third-party referee. In my previous post I suggested storing a web of known and co-operands for a specific class, as static data. This data would get passed down to some central repository when class-loading takes place.

Basically, the two Classes would get hashed, then the Andanator mapped to this hash's bucket. The question: where does this take place? Should it really take place inside static code belonging to various Pattern classes? Somewhere else? This code (Mapping classes to and operations) seems classless! Plus - where should this repository be stored? As static data in the Pattern interface? As static data in some abstract base class implementing Pattern? Somewhere else? This is what I am trying to figure out.

Cheers,

Fragorl

fragorla at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...
# 6
> marker interfaces?Could you elaborate please? I haven't been in the business long enough to know how these work (I've come across a few, I think - is EventListener one?)
fragorla at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...
# 7
That was meant to be"Could you elaborate please? I haven't been in the business long enough to know how these work;-) ..."
fragorla at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...
# 8
java.io.Serializable is a marker interface. It doesn't do anything (no methods) ... it just means that you want to let whatever it is participate willingly in what it is you want to do.
fragorla at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...
# 9
sorry about that market comment, initially i think it just popped up in reaction to comparing objects. http://www.martinfowler.com/articles/injection.htmlvisitor pattern also sound viable now, but beware
mchan0a at 2007-7-13 14:48:55 > top of Java-index,Other Topics,Patterns & OO Design...