object sameness, Leibniz's Law, substitutability, indistinguishability
John Rose
john.r.rose at oracle.com
Tue Aug 2 21:48:50 UTC 2022
On 27 Jul 2022, at 13:22, Kevin Bourrillion wrote:
> I've said this before, but I think both "substitutability" and
> "sameness" just lead to more questions, and I'm not sure why we don't
> appeal to distinguishability instead.
The short answer is there is no one-magic-word-no-more-questions
explanation for what we need to convey. More words will surely help,
but at some point you have to stop talking.
Except on Email threads. So, here’s a lot more talk from me on the
subject!
There are a number of words we could use here. “Substitutability”
is (AFAICR) a bow to Liskov, who uses it to talk about
correctness-preserving type changes at compile time, specifically
substituting an argument type S for for a parameter type C where S<:C,
when binding a method argument. From that POV we are bending it (just a
little) to talk about result-preserving reference changes at runtime.
But this term also occurs (along with the related term
“substitutivity”) in logic, law, and economics, with related
meanings.
In the context of software (and sometimes math and logic), “x is
substitutable for y” means “x can replace y”, and in an absolute
sense it captures the intuition that “x is just as good as y (for any
purpose we are now contemplating)”.
We could certainly pick other words out of the dictionary, and/or negate
the proposed predicate (defining x!=y instead of x==y) to allow other
related concepts like “[in-]distinguishability”, or “sameness”,
or “indiscernability”, or perhaps simply “unconditional
equality”. But choosing another word will probably not refine our
understanding of what’s going on; it will just shuffle around the
connotations and emphases, making some folks happier and other folks
more confused. Conversely, given a clearer shared understanding, we can
then proceed to choose any of a range of reasonable words to refer to
that understanding, including either or both of “substitutable” and
“indistinguishable”, or some other word.
But I’d like to back up first. Our problem with object equality, as I
understand it, begins with the word “same”. That little word
“same” is hardwired in the JLS and JVMS to talk about identity of
many kinds of things: tokens, source locations, classes, interfaces,
types, values, exceptions, packages, and more — in both static and
dynamic contexts (as appropriate). The word shows up several hundred
times, often with strongly normative force. That is, the specification
would be seriously broken without each such statement of “sameness”.
Occasionally sameness is defined explicitly, as in JLS 4.3.4, “When
Reference Types Are the Same”. But often the meaning (including
normative meanings) is left to the reader’s common sense, as in the
many places the spec talks about “same name” and “same
descriptor” (both referring to character sequences).
<https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.3.4>
But for object references the meaning of “same” is left to the
reader in JLS 15.21.3:
> At run time, the result of == is true if the operand values are both
> null or both refer to the same object or array; otherwise, the result
> is false.
<https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.21.3-200>
It seems to me that, in order to sharpen up the meaning of that use of
“same” (as well as others), a very good tool is something called
“Leibniz’s Law”, which simply says that two things are the same if
and only if there is no predicate that can distinguish them. That is,
x=y ⇔ ∀F(Fx ⇔ Fy).
<https://plato.stanford.edu/entries/identity-indiscernible/>
The application to software is immediate, since (in a software system)
any predicate can be represented as a procedure which accepts an input
and returns a boolean. (Obviously the procedure cannot just look at a
random number or changing global variable, or throw something; it has to
be immunized somehow against indeterminacy, lest our conceptual test
harness fail to produce the kind of result we want.) And the law’s
force is preserved if formulated in terms of many kinds of functions,
rather than predicates: x=y ⇔ ∀f(fx = fy), as long as the second
equality check is somehow more primitive; we Java folks could say it
must return an identity class or primitive.
So, for us, Leibniz’s Law defines that two software objects (or
values) are “the same” (identical in a purely logical sense) if and
only if no reasonable software function can tell them apart, by
returning different values for each of the two objects.
From there, it is a short step (a) to recognize that pre-Valhalla
reference equality satisfies Leibniz’s Law, and thus (b) to recognize
that this state of affairs can (and should) be preserved by choosing a
design much like we have today.
The law breaks into two parts, each part being its own separate
principle:
x=y ⇒ ∀f(fx = fy)
(If x equals y then applying any reasonable f to x and y gives the same
answer.)
This means that if two object references are evaluated (by the JLS and
JVMS) as referring to “the same object”, there must be no subsequent
computation which somehow extracts two different results from the two
references. Putting on our engineer’s hat, that means that if `acmp`
reports equality, the two inputs must have the same class (f =
`Object::getClass`) and pairwise “same” fields (f = various
`getfield` operations).
Specifically, the JVM is not allowed to get away with comparing some
pointers and returning “false” if the pointers refer to structurally
“same” objects which happen to be duplicated in the heap. It must
do the work of chasing the pointers.
(This principle breaks when applied to `==` on floating point numbers,
since +0.0 = -0.0 but f=`doubleToLongBits` returns distinct values.)
x≠y ⇒ ∃f(fx ≠ fy)
(If x doesn’t equal y then you can find a reasonable f to apply to x
and y, which gives different answers.)
This converse means that if two object references are evaluated as *not*
referring to the same object, then there must exist a computation which
can derive distinct values from them.
Specifically, we don’t just hardwire the `==` operator, much less the
“sameness” condition, to a user-defined `Foo::equal` method. Doing
this would require an infinite regress in the body of the method, if
(and when) it tries to ask if its operands are “the same” using
`==`.
(This principle breaks when applied to `!=` on floating point numbers,
since 0/0.=0/0. but there is no function which can distinguish 0/0. from
itself.)
Moving back to the problem at hand: Valhalla forces the specs to define
`acmp` and `==` in terms of same-class-and-fields for value objects. It
also (IMO) raises the question of whether we should try to tighten the
existing spec for identity objects (since there’s now a clear
contrast). I think it works to appeal to an object’s “identity”,
as a property of the object assigned at the first instant of the
execution of `new` (the operator or bytecode), and differently from
(“not the same as”) any other “identity” from any other
execution.
The specs do not *need* to refer to Leibniz’s Law, nor even appeal to
concepts of “substitutability” or “indistinguishability”, in
order to be operationally complete and sound. Email archives will hold
the present discussion basically forever, in case someone wants to
inquire why it’s the way it is.
But, the JVMS and JLS *should* (IMO) include some non-normative prose
that encourages the reader to think about intuitions like “once we
know x==y, then x is as good as y everywhere”, and also “if x==y in
one place, it will be impossible to tell them apart somewhere else”.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-observers/attachments/20220802/7bea1298/attachment-0001.htm>
More information about the valhalla-spec-observers
mailing list