Unique object identifier

Posted by Thomas Mercer-Hursh on 10-May-2010 20:44

Maybe I'm missing something, but ....

Give a Progress.Lang.Object  the ToString() method will return the fully qualified name followed by understcore followed by a unique object identifier.

This would appear to be a handy way to tell whether object A and object B were actually the same object, except ...

ToString() is a method name which is likely to be overriden somewhere else in the class hierarchy.  Give that the context in which I want this information is when the object is in a temp-table, i.e., it is just a PLO, I can't define some other property, e.g., a GUID, to uniquely identify the object because the method would not be available without casting.

Am I missing something?

All Replies

Posted by Peter Judge on 11-May-2010 07:43

tamhas wrote:

Maybe I'm missing something, but ....

Give a Progress.Lang.Object  the ToString() method will return the fully qualified name followed by understcore followed by a unique object identifier.

This would appear to be a handy way to tell whether object A and object B were actually the same object, except ...

ToString() is a method name which is likely to be overriden somewhere else in the class hierarchy.  Give that the context in which I want this information is when the object is in a temp-table, i.e., it is just a PLO, I can't define some other property, e.g., a GUID, to uniquely identify the object because the method would not be available without casting.

Am I missing something?

Why would you use ToString() for comparison purposes? There's an Equals() method on PLO which seems appropriate. It does a reference comparison in the absence of any overrides (ie 'handle to handle' if you will). A

You can do INT(objRef) which gives you an integer value of the reference, and you can also do a reference-to-reference comparison if you choose. This is nice in temp-tables because it's an indexed value. But it only works if you're doing identity matches, and not equality matches. In the latter case, you must use Equals().

-- peter

Posted by bsgruenba on 11-May-2010 09:16

Why would you use ToString() for comparison purposes? There's an Equals() method on PLO which seems appropriate. It does a reference comparison in the absence of any overrides (ie 'handle to handle' if you will). A

You can do INT(objRef) which gives you an integer value of the reference, and you can also do a reference-to-reference comparison if you choose. This is nice in temp-tables because it's an indexed value. But it only works if you're doing identity matches, and not equality matches. In the latter case, you must use Equals().

-- peter

Wait... So INT(objRef) gives you the equivalent of hPersistenProc:HANDLE? So if I was looking for the equivalent of java.lang.Object.hashCode, this would be it?

Posted by Peter Judge on 11-May-2010 09:23

So INT(objRef) gives you the equivalent of hPersistenProc:HANDLE?

Yes, but without the WIDGET-HANDLE() equivalent.

So if I was looking for the equivalent of java.lang.Object.hashCode, this would be it?

No - my understanding of what HashCode is/does is that it's more complex that that, and that it's more closely aligned with the equality side of things, rather than the identity.

-- peter

Posted by bsgruenba on 11-May-2010 09:57


So if I was looking for the equivalent of java.lang.Object.hashCode, this would be it?

No - my understanding of what HashCode is/does is that it's more complex that that, and that it's more closely aligned with the equality side of things, rather than the identity.

-- peter

From the Javadoc on hashCode():

public int hashCode()
Returns a hash code value for the object. This method is supported for the  benefit of hashtables such as those provided by  java.util.Hashtable.

The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution  of a Java application, the hashCode method must consistently return the  same integer, provided no information used in equals comparisons on the  object is modified. This integer need not remain consistent from one execution  of an application to another execution of the same application.
  • If two objects are equal according to the equals(Object) method,  then calling the hashCode method on each of the two objects must  produce the same integer result.
  • It is not required that if two objects are unequal according to the  equals(java.lang.Object) method, then calling the hashCode method on each of the two objects  must produce distinct integer results. However, the programmer should be aware  that producing distinct integer results for unequal objects may improve the  performance of hashtables.

As much as is reasonably practical, the hashCode method defined by class  Object does return distinct integers for distinct objects. (This is  typically implemented by converting the internal address of the object into an  integer, but this implementation technique is not required by the JavaTM programming language.)

Returns:
a hash code value for this object.
See Also:
equals(java.lang.Object)Hashtable

So, as I understand it, the default version of hashCode returns you an integer representation of the address. The first bullet and last statement sound exactly like the behavior of a OpenEdge handle.

The difference is, though, that hashCode() can be overridden in a Java class. So I guess the question I am really asking is can I use INT(obj) to produce the same value as the default implementation of hashCode? And I think what you are saying is "Yes".

Posted by Peter Judge on 11-May-2010 10:08

I agree that the last point seems to indicate that I'm saying 'yes', but I am really saying no.

Exhibit the First:

  • Whenever it is invoked on the same object more than once during an

execution  of a Java application, the hashCode method must

consistently return the  same integer, provided no information used

in equals comparisons on the  object is modified.


def var o1 as Progress.Lang.Object.
def var o2 as Progress.Lang.Object.

o1 = new Object().
o2 = new Object().

message int(o1) eq int(o2). /* will be false */

The example above is a little unfair, since PLO is somewhat special. If you substitute the class below and run it, the object references will be unequal, while calling Equals() will return true, and the HashCode values will be the same.


class foo:
def pub prop Value as char no-undo initial 'a value' get. set.

method public int HashCode():
iHash = TurnIntoHash(this-object:Value). /* TurnIntoHash to be implemented */

return iHash.
end method.

method override public logical Equals(po as Object):
return (po:Value eq this-object:Value).
end.
end class.

Exhibit the Second:

  • If two objects are equal according to the equals(Object) method,

then calling the hashCode method on each of the two objects must

produce the same integer result.

-- peter

Posted by bsgruenba on 11-May-2010 11:19

I think we're on the same page. In summary:

  • INT(obj) gives you the equivalent of the default hashCode() implementation.
  • If equals() is overridden, it may not do what you expect it to if you don't override hashCode(), too, which you can't in ABL because there is no hashCode on PLO (what an unfortunate acronym for Progress.Lang.Object. I can just see Yasser Arafat turning over in his grave).

Now, of course, you know what the next question is going to be...

When do we get hashCode() on PLO? Frankly, I was really surprised it wasn't included as part of the base hierarchy from the beginning.

Posted by Peter Judge on 11-May-2010 11:28

(what an unfortunate acronym for

Progress.Lang.Object. I can just see Yasser Arafat turning over in

his grave).

Tmh coined "PABLO" for Plain ABL Object, which I quite like. I've also heard POAO (Plain Old Abl Object). And you can certainly roll your own.

-- peter

Posted by Peter Judge on 11-May-2010 11:30

  • Whenever it is invoked on the same object more than once during an

execution  of a Java application, the hashCode method must

consistently return the  same integer, provided no information used

in equals comparisons on the  object is modified.

def var o1 as Progress.Lang.Object.
def var o2 as Progress.Lang.Object.

o1 = new Object().
o2 = new Object().

message int(o1) eq int(o2). /* will be false */



I think I misread this bullet: int(o1) will always equal int(o1), so in that sense, yes, it does apply.

However, I would say that this should be made explicit via a HashCode method on Progress.Lang.Object since it's then also overridable, and not an obscure artifact. Enhancement requests are always welcome

-- peter

Posted by bsgruenba on 11-May-2010 11:34

And not subject to

tail -f enhancement-request | /dev/null

?

Posted by Thomas Mercer-Hursh on 11-May-2010 11:46

Regardless of what Java does .... (WWJD = What Would Java Do?) ... it  seems to me that one wants two quite different things.

One  is to be able to tell whether one particular object reference is  actually the same object as some other object reference.  Int(ObjRef)  seems to do just that.

The other is to determine  whether two objects  are "equal" where "equal" is defined by the programmer.

As I read the doc, the default meaning of Equals is true only if the  two references point to the same object.  This is counter to my intuition which would be that Equals would be true for two objects of the same type with the same state.  One could, of course, override this in every object, but that seems chancy, so really Equals is nothing more than an alternative expression for Int(ObjRef) = Int(ObjRef).

Thus, to achieve the second goal, we are going to have to provide some new property that is implemented on all objects.

As I read the Javadoc, hashcode is shorthand implementation for the equals check as I have defined it above, *not* a shorthand for checking object identity.  I.e., hashcode varies by state, is programmer defined, and two separate objects with the same state (as defined by the programmer) have the same hashcode..

Posted by Thomas Mercer-Hursh on 11-May-2010 11:50

I think you were right the first time, Peter.  As I am reading that int(o1) at time T1 will always equal int(o1) at time T2, but for Java hashcode, that won't be the case if the state has changed between T1 and T2.

Posted by bsgruenba on 11-May-2010 11:50

I disagree with you about equals.

Equals defaults to the IDs matching, however, as a programmer you can override equals to mean whatever you want it to mean. The same is true with hashCode.

Posted by Thomas Mercer-Hursh on 11-May-2010 11:58

Yes, but what I am saying here is that Java isn't giving me what I as a programmer would really want.  What I want equals to be is equal state, regardless of whether it is the same object.

I.e., there should really be a property like ID or HANDLE where A.ID = B.ID if A and B are actually the same object.  We get this, less attractively, with Int(A) = Int(B).

Equals defaulting to be this same thing is not giving us anything extra.

What we really want separate from this IsSameObject test is HasSameState.  Default should be that all data members match in value, but I can override this if desired to exclude some data members or to add some fuzz around "match" or whatever.  Given its default, Equals() doesn't do this at all and is redundant on the Int(A) = Int(B) test.

Posted by bsgruenba on 11-May-2010 11:58

tamhas wrote:

I think you were right the first time, Peter.  As I am reading that int(o1) at time T1 will always equal int(o1) at time T2, but for Java hashcode, that won't be the case if the state has changed between T1 and T2.

Only if you alter the default implementation of HashCode().

Posted by Peter Judge on 11-May-2010 12:07

What we really want separate from this IsSameObject test is HasSameState.  Default should be that all data members match in value, but I can override this if desired to exclude some data members or to add some fuzz around "match" or whatever.  Given its default, Equals() doesn't do this at all and is redundant on the Int(A) = Int(B) test.

IsSameObject = identity: int(o1) eq int(o2)

HasSameState = equality: o1:Equals(o2)

Equals() should not be a constant: it should take the current state into account. I would submit, as should HashCode (of course, if you're using it as a key, you'd probably want an immutable object otherwise you're kinda up a certain creek without a paddle).

I bookmarked this as interesting: http://www.ibm.com/developerworks/java/library/j-jtp05273.html#write

-- peter

Posted by Admin on 11-May-2010 12:13

tail -f enhancement-request | /dev/null

 

Is that your experience from the inside? :-o

Posted by Thomas Mercer-Hursh on 11-May-2010 12:44

Depends,on your point of view.  To me, if equals() defaults to meaning that it is the same object, but equals is overridable, then equals becomes meaningless if not overriden and unreliable unless consistently overriden to mean equal state.  In the same sense, if hashcode is not overriden to include state, then I don't see that it has a consistent and useful meaning beyond int(OR).

I.e., it doesn't seem to me that Java is giving us a useful model here for fulfilling the two goals I set out.  If the default implementation of all of the options only tells us about object identity, then not only are they redundant, but they are unreliable because, being overridable, they can't even be consistently used to tell us about object identity.

Seems to me that the need is for a consistent, reliable test of object identity PLUS a test that two separate objects are identical in programmer defined terms.  And, the default for the latter should be all state for the object.

Posted by bsgruenba on 11-May-2010 13:26

Not at all... I have seen PSC implement stuff that I really wish they hadn't because customers drove it!

I also know that unless you make a really good case for something, it will probably be ignored, which is the way it should be. The problem is, I am having a hard time articulating what my case for this is right now. I need to sit down and justify it properly before I submit the enhancement request.

And, yes, this is low-hanging fruit, so it should be the simplest thing in the world to implement.

Posted by Thomas Mercer-Hursh on 11-May-2010 14:36

After some off list exchange, let me bring this back with some clarification.

As I have said, what I would like to have is two things

1) The ability to tell that two object references actually point to the same object.

2) The ability to tell that two object references point to objects which have the same state, regardless of whether they are the same object.  I.e., one is a clone of the other.

The first one we have in ABL with int(O1) = int(O2) ... it just isn't very elegant.  It would be nice if PSC would implement a property one PLO called ID or Handle or something which was int(self) so we could make the comparison more nicely.  Yes, I could create Son of PLO as a base class for all my classes, but unless there was something else interesting to put in there that didn't belong in PLO, I would rather have it in PLO.

The Java versions of the second one, Equals and Hashcode, are unappealing to me because their default behavior is that two objects are only equal if they are in fact the same object.  Not only is this redundant with int(o), but it implies that, for these members to mean anything else, I have to override the behavior in EVERY CLASS!  Maybe I'm lazy, but I like default behaviors to be the behavior I actually want most of the time so that I only have to override to get a different behavior.

What I would like for a default is for a function that would compare all the values of all of the data members and tell me if they all matched.  Implemented directly, that would take rather stronger reflection than we currently have available.  However, IF PSC were to give us WRITE-XML on an object which would create an XML string containing the values of all the datamembers ... which, of course, would be a lovely, lovely tool for serializing objects for transmission over the wire and capturing the state of an object for possible rollback and such (Memento pattern) and putting an object into a database and all sorts of other useful things ... then something like

SHA1-DIGEST(self:WRITE-XML)

would provide us with a great basis for making that comparison.

So, ideal would be to have PSC implement WRITE-XML and READ-XML on objects and then to define something like Hashcode as a property on PLO which did the SHA1-DIGEST(self:WRITE-XML) computation ... and think how lovely that would be.  Not the same as the Java hashcode, but better.

Posted by Peter Judge on 11-May-2010 15:18

The first one we have in ABL with int(O1) = int(O2) ... it just isn't

very elegant. It would be nice if PSC would implement a property one

PLO called ID or Handle or something which was int(self) so we could

make the comparison more nicely.

IMO, the reference itself shouldn't really be exposed. PLO:Equals() can do this comparison for you, and if you want to do that check first in your Equals() - should you have one - you can. But you should really be calling super:Equals(): that's what it's there for. It should encapsulate the equality match.

Not only is this

redundant with int(o), but it implies that, for these members to mean

anything else, I have to override the behavior in EVERY CLASS! Maybe

I'm lazy, but I like default behaviors to be the behavior I actually

But you don't want the default behaviour ... you want YOUR behaviour (per class). So I don't get what the problem is.

What I would like for a default is for a function that would compare

all the values of all of the data members and tell me if they all

matched.

All the members? That's analogous to one DB record only being equal to another if and only if all of the fields are identical. There's a reason we have primary unique keys.

-- peter

Posted by Thomas Mercer-Hursh on 11-May-2010 15:48

The problem with relying on PLO:Equals() is that it is overridable, is it not?  So, if what I really want to know is whether two object references are pointing to the same thing, Equals is not reliable. That's why I want an immutable property ... or for Equals to be FINAL.

Also, one can store PLO.ID, but one has nothing to store with PLO.Equals() except the boolean result, which tells you nothing later.

But you don't want the default behaviour ... you want YOUR behaviour  (per class). So I don't get what the problem is.

What I want, in general, is a way to tell if one object is effectively a clone of another.

In some cases, I might want to exclude some property from that comparison.  E.g., if I had a class in which there was a datetime stamp for when it was created, I might want to exclude that from the test.

In the Java, Equals and Hashcode default behavior tells me that two references are the same ONLY if they are the same object.  Thus, in order to get a comparison of values, I have to override EVERY class.

The ABL Equals has the same default behavior as the Java Equals and thus the same defect.  Moreover, if I do override it in every class to give my desired behavior, then I lose its potential value for checking for the same instance.

All the members? That's analogous to one DB record only being equal to  another if and only if all of the fields are identical. There's a reason  we have primary unique keys.

Yes, all the members.  The equivalent of unique key constraints is a different property altogether.

In fact, think about it in terms of the kind of update cycle that one might do with a DB record.  I start with an object and it has a particular state, corresponding, perhaps, to all the fields in a particular database record.  I then clone it or make a memento to record its before image state.  Then I do something to it ... e.g., send it to the UI for processing or some BL action.  When I get it back, it stil has the same identity it started out with.  If I am using arbitrary keys like a GUID as a primary index, it still has that same value.  But, has it changed from what it was when it started out.  A comparison with the clone or memento will tell me whether it has changed or not.  Equals() does nothing for me here.

And, hey, whether or not you would do things this same way, you have to admit that WRITE-XML and READ-XML on all data members of a class would be very cool for a lot of purposes and a property equivalent to SHA1-DIGEST(self:WRITE-XML) would be a great way to get a value for an object that would tell you whether or not it had changed and whether or not a possible clone was or was not a true clone.  Seems like both of these should be very easy to add.  10.2B02 maybe?

Posted by Thomas Mercer-Hursh on 11-May-2010 16:55

One additional thought.  If one serializes an object and then stores it, sends it across the wire, or whatever and then at some later point reconstitutes it, the int(ObjRef) will be different from the original.  But, if one has done something like pass this object to the client and the client has passed it back,  one would want a way to connect the new object with the original.  One way to do this would be to have a property like GUID which was included in the serialization and thus could be set if the object was reconstituted from a serialized form, i.e., a private setter.  This seems like another trivially easy and useful property for PLO.

This thread is closed