(late to the party)
Thanks for pulling this together. That people routinely implement
equals/hashCode explicitly is something we would like to put in the past.
Let me make a few points regarding connection with future potential features,
not because I want to say that these will obsolete Equivalence before it
starts, or that we should delay working on it until we have these features —
just to point out areas of potential overlap so we can pay attention to how
these things might converge.
1. Pattern matching. Pattern matching offers a path to a better way to write
equals() methods, without the complex control flow that is typical of
template-expanded implementations (whether expanded by a human or an IDE.). For
a class like Point, we can implement equals via:
boolean equals(Object o) {
return o instanceof Point p
&& p.x == x
&& p.y = y;
}
This is no less explicit than the status quo, but more readable and less
error-prone (no short circuit tests; one big &&'ed expression.). However,
pattern matching offers little help for implement hashCode in a comparable way.
2. Pattern matching, again. The implementation of a nontrivial pattern is a
class member. For sake of exposition, imagine it is declared like this
(please, we’re not discussing the syntax of this now):
class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public pattern Point(int x, int y) {
x = this.x;
y = this.y;
}
}
(and of course records will automate this.). Given that a pattern is a way of
extracting a bundle of state from an object, assuming there were some way to
refer symbolically to a pattern, one could derive an Equivalence from a
“pattern reference”:
Equivalence.of(<pattern ref for Point deconstruction pattern>);
3. Field references. We like the Comparator factories and combinators well
enough, which take lambdas that extract single fields. It’s a short hop to
accepting field references, which would be both more pleasant to use, and more
optimizable (field references are more amenable to constant-folding
optimizations, for example, by virtue of their transparency.)
4. Specialized generics. The rift between objects and primitives was the
major pain-generator for all of the Java-8-era APIs, including the Comparator
APIs. Not only did we need hand-specialized comparingInt() methods, but the
lack of a common super type without boxing meant that we could not use the more
appealing approach of varargs, but instead had to have a method call for each
component in the comparison. You are in the same boat now, until Valhalla
delivers specialized generics.
Its worth thinking a bit about what we would like the long-term API to look
like, so we can steer clear of getting in its way between now and then. With
specialized generics, we’d probably want something like
static<T> Equivalence<T> of(Class<T> clazz, Function<T,?>… components)
Which suggests we probably want to steer away from having a varargs option, so
that we are not buying ourselves one more migration headache.
> On Apr 22, 2019, at 2:29 PM, Liam Miller-Cushon <[email protected]> wrote:
>
> Please consider this proposal for a library to help implement equals and
> hashCode.
>
> The doc includes a discussion of the motivation for adding such an API to the
> JDK, a map of the design space, and some thoughts on the subset of that space
> which might be most interesting:
>
> http://cr.openjdk.java.net/~cushon/amber/equivalence.html
> <http://cr.openjdk.java.net/~cushon/amber/equivalence.html>