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 ===.
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:
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
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
I may have forgotten to raise something else, but there's that for now.
-- Darren Duncan