At 7:25 PM +0300 7/12/06, Yuval Kogman wrote:
Over at #perl6 we had a short discussion on =:=, ===, and ~~, mostly raised by
ajs's discussion on Str items and ===.
<snip>

Coincidentally, I raised almost the same questions there a week earlier, and had a brief discussion with audreyt about it, though the answers that came out of it seemed rather different than what was in this thread so far, so I will share them. See the following url:

http://colabti.de/irclogger/irclogger_log/perl6?date=2006-07-06,Thu&sel=376#l599

I will also quote the text as it was short, snipping out unrelated parts:

[ 11:29pm ] dduncan : slight change of topic, but I was wondering how .id works with non-trivial types
[ 11:29pm ] dduncan : eg, what does the .id of a Pair look like?
[ 11:29pm ] dduncan : I know that to users it shouldn't matter, but to people implementing composite types, it does
[ 11:30pm ] audreyt : dduncan: one possibility - could be just itself.
[ 11:33pm ] dduncan : one key thing I'm wondering about .id for immutable types is ... are they supposed to generate some neutral value like an integer, two of which can then be compared independently of the type definition, or will they contain references to the actual object all the time and that the object's class still needs to declare a === method which is invoked as needed? [ 11:33pm ] dduncan : if it is the latter, I imagine that implementation will be simpler, at a possible cost of performance if the same comparison is done a lot
[ 11:34pm ] audreyt : dduncan: the latter
[ 11:34pm ] dduncan : okay, that answers my question

So, in the general case, it would seem best if the binary operator === was just an ordinary method that each class provides, rather than requiring classes to defined a .id. Or in addition to this to help with performance, a .id can exist anyway that optionally returns an appropriate hash of an object.

A default === would be defined in Object, which returns the same result as =:= returns; two objects are equivalent iff they are the same container. A default .id defined in Object would simply return the same object it was invoked on.

Built-in immutable types, like Str and Int and Pair and Seq, would override that === such that they return true iff the two operands are containers of the same class and the two containers both hold appearances of the same (universally distinct) value. This is determined by doing a deep comparison of the values themselves, as is appropriate. (Internally to the type's implementation, a domain-appropriate hash of the value could optionally be generated at an appropriate time and be used to speed up === operations, with appropriate action taken if it isn't guaranteed that multiple distinct values won't become identical hash values.) The .id could be overridden to return a simple number or string or binary for simpler types, and return the object itself otherwise.

Built-in mutable types, like Array or Hash, would not override the Object-defined ===, which is equivalent to =:=, nor the built-in .id, which returns the object itself. This is reasonable in practice because the contents of those containers could be changed at any time, especially if the containers are aliased to multiple variables that are outside of the testing code's control. The only thing that can be guaranteed to be constant over time is that whether or not an object is itself, as determined by =:=. By contrast, if === were to do a deep copy with mutable types, the results could not be trusted to be repeatable because the moment after === returns, the container's value may have changed again, so actions done based on the === return value would be invalid if they assumed the value to still be the same at that time, such as if the mutable type was used as a hash key and was to be retrievable by its value.

User defined types can choose on their own whether to override === and/or .id or not, and they would use their own knowledge of their internal structures to do an appropriate deep comparison. There is no need to try to generate some kind of unique numerical .id for arbitrarily complex objects.

One thing that can't be overridden is that === can only return true iff both operands are of the same class. This includes undef, as each class has its own undef that is distinct from those of other classes.

So if this is the way that things worked, then it would be very easy to implement it for any kind of type. And it would be very reliable to use any type as a hash key.

Note that, while the fact may be determinable by some other means, it may be useful to have an explicit meta-method for all types that says whether the type is immutable or mutable. A user defined type saying that it is immutable is making a promise to the compiler that its objects won't change after they are created.

As for being able to tersely do deep comparisons of mutable types, I don't think that === is appropriate and that something else should be used instead, something that isn't invoked when working with hash keys.

I may have forgotten to raise something else, but there's that for now.

-- Darren Duncan

Reply via email to