(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 <cus...@google.com> 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>

Reply via email to