Exotic classes

681 views
Skip to first unread message

Remi Forax

unread,
Feb 26, 2018, 2:29:19 PM2/26/18
to mechanical-sympathy
Hi all,
i'm preparing a talk at DevoxxFR on how to make a field value, a returned value, etc constant for any decent JITs (everything but c1), so i've bundled together several patterns i use for implementing dynamic language runtimes into an Java API

https://github.com/forax/exotic

I would like to have your comments about those exotic classes (it's already has been done, it's stupid, it's not thread safe, etc)

regards,
Rémi



Steven Stewart-Gallus

unread,
Jan 17, 2019, 2:41:32 AM1/17/19
to mechanical-sympathy
I've been working on similar issues trying to optimise something
heavily.  I made a similar class to this one (I even had a similar
API) but I found I called it MostlyFinal instead.

private static final MostlyConstant<Integer> FOO = new MostlyConstant<>(42, int.class);
private static final IntSupplier FOO_GETTER = FOO.intGetter();

By the way using a different interface than Supplier can give the JVM
more class hierarchy analysis info and so potentially allow for
inlining even without static final.

You can also simply use a closure in some cases sort of like this:

interface IntBox {
   int get();
}
public IntBox makeBox(int x) {
    return () -> x;
}

This is better for inlining because the JVM trusts final fields in VM
anonymous classes more than yours.  Unfortunately
TrustStaticFinalFields cannot be a thing by default yet for backwards
compatibility reasons.

I think a lot of these things are pretty neat but unfortunately hard
to package in a generic and usable library because people delving into
these will want to tear into all the internal details for maximum
performance.

I don't really understand your StableField class.  How is it supposed
to be any faster than MostlyConstant?  I would suggest if you wanted
the best speed (in some ways and at a cost of memory) you could spin a
static final class with a method that returns a constantdynamic entry
and then return a methodhandle to that entry.  This seems possibly
heavyweight IMO so I'm still thinking about this myself.

If StringSwitchCallSite being a MutableCallSite seems possibly
unneeded with a reworked API to me.

I am highly suspicious TypeSwitch will increase performance in most cases.
instanceof checks are highly optimized and give info that allow
further optimizations.

You might want to consider using/abusing the JVM's own inline caching
behaviour for interfaces for some dispatching.

It's not too hard to create a bag of interface implementations at
runtime that all dispatch to separate CallSite implementations which
can be faster than exactInvoker/your own MethodHandle lookup logic
sometimes.  I considered this for a ThreadLocalCallSite class I was
making but I'm still not sure about the design.

So basically one hack to get quicker thread local behaviour is to
subclass Thread and add your own fields/methods like this:

((MyIface)Thread.currentThread()).doSpecific();

If you add your own bag of interface implementations then you can do
this dynamically:

MY_IFACE.invokeExact((BagThread)Thread.currentThread()).bag, ...);

I'm not sure about the bytecode generation here though. I don't want
to be too blase about that.

It looks like you have some benchmarks setup but I don't see any txt
files with any perf data listed.  I mentioned a lot of gibberish
earlier but problem my biggest advice would be to add more benchmarks
and look at your benchmarks again and also get real world usage data.

Remi Forax

unread,
Jan 18, 2019, 8:37:58 AM1/18/19
to mechanical-sympathy
De: "Steven Stewart-Gallus" <stevensele...@gmail.com>
À: "mechanical-sympathy" <mechanica...@googlegroups.com>
Envoyé: Jeudi 17 Janvier 2019 07:50:13
Objet: Re: Exotic classes
I've been working on similar issues trying to optimise something
heavily.  I made a similar class to this one (I even had a similar
API) but I found I called it MostlyFinal instead.

private static final MostlyConstant<Integer> FOO = new MostlyConstant<>(42, int.class);
private static final IntSupplier FOO_GETTER = FOO.intGetter();

By the way using a different interface than Supplier can give the JVM
more class hierarchy analysis info and so potentially allow for
inlining even without static final.

no, CHA only works on class, not on interface.



You can also simply use a closure in some cases sort of like this:

interface IntBox {
   int get();
}
public IntBox makeBox(int x) {
    return () -> x;
}

This is better for inlining because the JVM trusts final fields in VM
anonymous classes more than yours.  Unfortunately
TrustStaticFinalFields cannot be a thing by default yet for backwards
compatibility reasons.

I think a lot of these things are pretty neat but unfortunately hard
to package in a generic and usable library because people delving into
these will want to tear into all the internal details for maximum
performance.

I don't really understand your StableField class.  How is it supposed
to be any faster than MostlyConstant? 

It's not the same semantics, you can not change a StableField more than once so you have the guarantee that once the field is initialized, no deoptimization can occur.
The other things is the object is not constant, you will get an exception so it's hard to misuse that API.
The last point is that i expect that at some point i will change the implementation so the slowpath will cost less than the slowpath of MostlyConstant, but i've never had the time to think how to do that in a VM independant way (i know how to do that with Hotspot only).


I would suggest if you wanted the best speed (in some ways and at a cost of memory) you could spin a
static final class with a method that returns a constantdynamic entry
and then return a methodhandle to that entry.  This seems possibly
heavyweight IMO so I'm still thinking about this myself.

better, use a lazy static final field (see https://bugs.openjdk.java.net/browse/JDK-8209964),
i.e. tech javac to either emit a ldc to the ConstantDynamic or do a getfield that will trigger the ConstantDynamic initialization if needed.



If StringSwitchCallSite being a MutableCallSite seems possibly
unneeded with a reworked API to me.

but it means that you have if/else branch for String that the program has never encounter.
I prefer to not add all the branches statically but add then at runtime dynamically when i know i need then.



I am highly suspicious TypeSwitch will increase performance in most cases.
instanceof checks are highly optimized and give info that allow
further optimizations.

It depends, if instanceof else ... is slow if you have deep hierarchy, if the test are not in the right order of occurence and if you have too many branches.
But yes, the StringSwitch and the TypeSwitch can be slower/faster than the equivalent cascade of if/else.



You might want to consider using/abusing the JVM's own inline caching
behaviour for interfaces for some dispatching.

A MethodHandles.guardWithTest is exactly that !

It's not too hard to create a bag of interface implementations at
runtime that all dispatch to separate CallSite implementations which
can be faster than exactInvoker/your own MethodHandle lookup logic
sometimes.

No, believe me, it's hard. It's the reason i've created (with others) the java.lang.invoke API.

I considered this for a ThreadLocalCallSite class I was
making but I'm still not sure about the design.

So basically one hack to get quicker thread local behaviour is to
subclass Thread and add your own fields/methods like this:

((MyIface)Thread.currentThread()).doSpecific();

If you add your own bag of interface implementations then you can do
this dynamically:

MY_IFACE.invokeExact((BagThread)Thread.currentThread()).bag, ...);

It will not be inlined by the VM :(

About having a ThreadLocalCallSite, as part of project Loom, we are discussing about how associate a value to a part of the callstack.
Anyway, the VM doesn't have a code cache per thread, there is only a global code cache.

I'm not sure about the bytecode generation here though. I don't want
to be too blase about that.

It looks like you have some benchmarks setup but I don't see any txt
files with any perf data listed.  I mentioned a lot of gibberish
earlier but problem my biggest advice would be to add more benchmarks
and look at your benchmarks again and also get real world usage data.

yes,
at least i should add the benchmark result in the javadoc.

regards,
Rémi

Steven Stewart-Gallus

unread,
Jan 18, 2019, 11:19:06 PM1/18/19
to mechanical-sympathy


On Friday, January 18, 2019 at 5:37:58 AM UTC-8, Rémi Forax wrote
no, CHA only works on class, not on interface.
You're probably know better than me. I seem to remember there's something like that for interfaces but very limited such as if you ever only have ever one implementation. I suppose you could create a class at runtime that fills out an abstract class which implements Supplier. You'd just create a class with a static final field that returns it as an abstract method impl.
 
It's not the same semantics, you can not change a StableField more than once so you have the guarantee that once the field is initialized, no deoptimization can occur.
The other things is the object is not constant, you will get an exception so it's hard to misuse that API.
The last point is that i expect that at some point i will change the implementation so the slowpath will cost less than the slowpath of MostlyConstant, but i've never had the time to think how to do that in a VM independant way (i know how to do that with Hotspot only).

Probably better just to omit it until you can actually make it behave differently. By the way, how can you get Hotspot to do it?

but it means that you have if/else branch for String that the program has never encounter.
I prefer to not add all the branches statically but add then at runtime dynamically when i know i need then. 

You just have to be extra lazy about it. It's usually bad style but nothing prevents you from doing something like:

private static final class Helper {
   
static final Config CONFIG = initConfig(configStr);
}
private String configStr = null;

public static Config getConfig() {
   
return Helper.CONFIG;
}

private static Config initConfig(String initStr){
   
// ...
}

 
It will not be inlined by the VM :(

Yeah I abandoned trying to make my own ThreadLocalCallSite implementation for now.  I couldn't work out many of the details.
 

About having a ThreadLocalCallSite, as part of project Loom, we are discussing about how associate a value to a part of the callstack.
Anyway, the VM doesn't have a code cache per thread, there is only a global code cache.

It seems like for a lot of things it is just simpler to have separate isolated processes. I wonder if something like Graal could be hacked to have separate separate Java subinterpreters for separate threads.

Another option is to clone key classes/methods per core but I wasn't sure about how heavyweight that would be.

Remi Forax

unread,
Feb 6, 2019, 6:12:42 PM2/6/19
to mechanical-sympathy
Hi Steven,


De: "Steven Stewart-Gallus" <stevensele...@gmail.com>
À: "mechanical-sympathy" <mechanica...@googlegroups.com>
Envoyé: Samedi 19 Janvier 2019 05:19:06
Objet: Re: Exotic classes


On Friday, January 18, 2019 at 5:37:58 AM UTC-8, Rémi Forax wrote
no, CHA only works on class, not on interface.
You're probably know better than me. I seem to remember there's something like that for interfaces but very limited such as if you ever only have ever one implementation.

You have been prescient :)
c2 now does CHA on interfaces.


[...]

Rémi

Carl Mastrangelo

unread,
Apr 22, 2019, 2:25:02 PM4/22/19
to mechanical-sympathy
These classes (especially MostlyConstant) are pretty cool.  I have some questions about them:

1.  In MostlyConstant I noticed that modifying the constant doesn't call MutableCallSite.syncAll().   Should it?

2.  A followup to #1: MutableCallSite.syncAll() doesn't actually seem implemented in OpenJDK.  Is it correct to call it?

3.  In my non-scientific JMH measurements, it seems like modifying the call site takes about 1us.   Does that sound about right?   From what I can tell modifying the constant is akin to deoptimizing the code and recompiling it, which means that 1us seems really fast.    

Nitsan Wakart

unread,
Apr 22, 2019, 2:31:19 PM4/22/19
to mechanica...@googlegroups.com
The cost would be a combination of the deopt, cost of slow down, and cost of compilation all of which are variable based on the generated code which embeds the constant and the compiler used. 
--
You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-symp...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Remi Forax

unread,
Apr 22, 2019, 3:37:19 PM4/22/19
to mechanical-sympathy
De: "mechanical-sympathy" <mechanica...@googlegroups.com>
À: "mechanical-sympathy" <mechanica...@googlegroups.com>
Envoyé: Lundi 22 Avril 2019 20:25:01
Objet: Re: Exotic classes
These classes (especially MostlyConstant) are pretty cool.  I have some questions about them:
1.  In MostlyConstant I noticed that modifying the constant doesn't call MutableCallSite.syncAll().   Should it?

yes, you're right from a spec POV i should call syncAll(),
the thing is that the current OpenJDK implementation doesn't need syncAll, but it may change in the future or for another implementation.


2.  A followup to #1: MutableCallSite.syncAll() doesn't actually seem implemented in OpenJDK.  Is it correct to call it?

yes, it's not implemented because the native part of setTarget() do the deopt, but it's an implementation detail.



3.  In my non-scientific JMH measurements, it seems like modifying the call site takes about 1us.   Does that sound about right?   From what I can tell modifying the constant is akin to deoptimizing the code and recompiling it, which means that 1us seems really fast.    

it's quite fast but it may be because
- it may be not optimized yet
- the VM mark all the different generated assembly codes that reference the constant as should be removed from the code cache and will do it later,
- the VM will not re-optimize directly if a code is deoptimized, but jump in the interpreter and re-optimize later.

so there is a good chance that what you are measuring is not all de-optimization cost.

Rémi



On Monday, February 26, 2018 at 11:29:19 AM UTC-8, Remi Forax wrote:
Hi all,
i'm preparing a talk at DevoxxFR on how to make a field value, a returned value, etc constant for any decent JITs (everything but c1), so i've bundled together several patterns i use for implementing dynamic language runtimes into an Java API

  https://github.com/forax/exotic

I would like to have your comments about those exotic classes (it's already has been done, it's stupid, it's not thread safe, etc)

regards,
Rémi



Carl Mastrangelo

unread,
Apr 23, 2019, 12:23:48 PM4/23/19
to mechanical-sympathy
Replies inline


On Monday, April 22, 2019 at 12:37:19 PM UTC-7, Remi Forax wrote:



De: "mechanical-sympathy" <mechanica...@googlegroups.com>
À: "mechanical-sympathy" <mechanica...@googlegroups.com>
Envoyé: Lundi 22 Avril 2019 20:25:01
Objet: Re: Exotic classes
These classes (especially MostlyConstant) are pretty cool.  I have some questions about them:
1.  In MostlyConstant I noticed that modifying the constant doesn't call MutableCallSite.syncAll().   Should it?

yes, you're right from a spec POV i should call syncAll(),
the thing is that the current OpenJDK implementation doesn't need syncAll, but it may change in the future or for another implementation.


2.  A followup to #1: MutableCallSite.syncAll() doesn't actually seem implemented in OpenJDK.  Is it correct to call it?

yes, it's not implemented because the native part of setTarget() do the deopt, but it's an implementation detail.



3.  In my non-scientific JMH measurements, it seems like modifying the call site takes about 1us.   Does that sound about right?   From what I can tell modifying the constant is akin to deoptimizing the code and recompiling it, which means that 1us seems really fast.    

it's quite fast but it may be because
- it may be not optimized yet
- the VM mark all the different generated assembly codes that reference the constant as should be removed from the code cache and will do it later,
- the VM will not re-optimize directly if a code is deoptimized, but jump in the interpreter and re-optimize later.

I was running this in a JMH benchmark, and I inspected the Assembly and Compiler output; I believe it was reaching c2 before swapping.  

A followup question: does deoptimization mean it reverts to a C1 copy of the code, or directly back to the interpreter?  i.e. how much work does it have to undo?  

Remi Forax

unread,
Apr 24, 2019, 7:05:52 AM4/24/19
to mechanical-sympathy
no, for a method, the code generated by c1 is often long gone at the time the code generated by c2 detects that it should deopt.
And c1 like c2 generates the code optimistically so even if the code generated by c1 was around, there is a good chance it will have to be de-optimized too.

BTW, i've just added a way to have equals and hashCode automatically implemented to exotic [1].

Rémi


 

so there is a good chance that what you are measuring is not all de-optimization cost.

Rémi



On Monday, February 26, 2018 at 11:29:19 AM UTC-8, Remi Forax wrote:
Hi all,
i'm preparing a talk at DevoxxFR on how to make a field value, a returned value, etc constant for any decent JITs (everything but c1), so i've bundled together several patterns i use for implementing dynamic language runtimes into an Java API

  https://github.com/forax/exotic

I would like to have your comments about those exotic classes (it's already has been done, it's stupid, it's not thread safe, etc)

regards,
Rémi


Steven Stewart-Gallus

unread,
Apr 25, 2019, 7:51:47 PM4/25/19
to mechanical-sympathy
1. Why
public abstract class ObjectSupport {
   
public abstract boolean equals(Object self, Object other);
   
public abstract int hashCode();
   
public static ObjectSupport of(Lookup lookup, String... fields) {
       
// impl details
    }
   
// impl details
}

and not something like?
interface ObjectSupport<T> {
   
public boolean equals(T self, T other);
   
public int hashCode(T obj);
   
public static <T, U extends ObjectSupport<T>> T of(Lookup lookup, Class<U> iface, Class<T> obj) {
       
// impl details
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface ObjectSupportField {
}

2. I don't understand why you can't use https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/MethodHandles.Lookup.html#defineClass-byte:A- instead of unsafe. If you can use https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html things might be easier to optimise because the VM doesn't trust nonstatic final fields but I don't think you'll need to rely on that.
3. The raw class file isn't always available at runtime so you can't necessarily use ObjectSupportImpl as a template.
4. https://github.com/forax/exotic/blob/master/src/main/java/com.github.forax.exotic/com/github/forax/exotic/ObjectSupport.java#L180 Pretty sure you want a static final field here. You can do that if you're using ObjectSupportImpl as a raw template. Unfortunately defineClass doesn't accept arguments so you have to use wonky garbage like a hashmap to pass runtime data that can't be embedded in a class file easily.

Remi Forax

unread,
Apr 26, 2019, 11:13:17 AM4/26/19
to mechanical-sympathy
De: "Steven Stewart-Gallus" <stevensele...@gmail.com>
À: "mechanical-sympathy" <mechanica...@googlegroups.com>
Envoyé: Vendredi 26 Avril 2019 01:51:47
Objet: Re: Exotic classes

Hi Steven,
thanks for spending some time on this,


1. Why
publicabstractclassObjectSupport{
publicabstractboolean equals(Objectself,Object other);
publicabstractint hashCode();
publicstaticObjectSupport of(Lookup lookup,String... fields){
// impl details
}
// impl details
}

and not something like?
interfaceObjectSupport<T>{
publicboolean equals(T self, T other);
publicint hashCode(T obj);
publicstatic<T, U extendsObjectSupport<T>>T of(Lookup lookup,Class<U> iface,Class<T> obj){
// impl details
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interfaceObjectSupportField{
}


There are three questions, why not use an interface, why not use generics to make the API more typesafe and why not use an annotation to mark the fields,
(1), it should be an interface, yes :
(2), yes, the API can be more typesafe, i've implemented that. BTW, the correct type of equals() if generified is (T, Object), because you can call equals(Person, String), it should return false.
(3),the API you propose can be built on top of the existing one, that's why there is an overload of ObjectSupport.of() that takes a function as third parameter

so i've updated the code according to (1) and (2).

2. I don't understand why you can't use https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/MethodHandles.Lookup.html#defineClass-byte:A- instead of unsafe. If you can use https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html things might be easier to optimise because the VM doesn't trust nonstatic final fields but I don't think you'll need to rely on that.

I need a VM anonymous class otherwise the method handles used to implement equals and hashCode are not considered as constant by the JIT. A previous version of the API was using one lambdas for equals and one for hashCode,

3. The raw class file isn't always available at runtime so you can't necessarily use ObjectSupportImpl as a template.

yes, i will inline it (storing it as an array of bytes inside the Java code) once the API is stable enough.

4. https://github.com/forax/exotic/blob/master/src/main/java/com.github.forax.exotic/com/github/forax/exotic/ObjectSupport.java#L180 Pretty sure you want a static final field here. You can do that if you're using ObjectSupportImpl as a raw template. Unfortunately defineClass doesn't accept arguments so you have to use wonky garbage like a hashmap to pass runtime data that can't be embedded in a class file easily.

No need for static fields there because ObjectSupportImpl is loaded as a VM anonymous class, so the instance fields are considered as constant if the object itself is a constant (see the answer to your question 2).

There is a JMH test if you want to take a look to the generated assembly code

Steven Stewart-Gallus

unread,
Apr 26, 2019, 6:15:07 PM4/26/19
to mechanica...@googlegroups.com
Hi Remi,

It seems to me you have to do a lot of hacky stuff to get around the ugly API.

Maybe it'd be better to separate out the ObjectSupport class into two separate classes, a HasherSupport class and an EqualsSupport class.

interface HashSupport<T> {
   
public static <T> HashSupport<T> of(Lookup lookup, String... fields) {
       
var mh = ObjectSupportImpl.createHasher(lookup, fields);
       
return (obj) -> {
            mh
.invokeExact(obj);
       
};
   
}
   
int hashCode(T obj);
}

and similar for EqualsSupport.

I feel like ObjectSupport is a bit nebulous and open ended and you'd inevitably end up needing more support methods such as a toString method.

This is pretty neat stuff.

To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-sympathy+unsub...@googlegroups.com.

Remi Forax

unread,
Apr 27, 2019, 6:05:05 AM4/27/19
to mechanical-sympathy
De: "Steven Stewart-Gallus" <stevensele...@gmail.com>
À: "mechanical-sympathy" <mechanica...@googlegroups.com>
Envoyé: Samedi 27 Avril 2019 00:15:07
Objet: Re: Exotic classes


It seems to me you have to do a lot of hacky stuff to get around the "ugly" API as you call it.

Maybe it'd be better to separate out the ObjectSupport class into two separate classes, a HasherSupport class and an EqualsSupport class.

interfaceHashSupport<T>{
publicstatic<T>HashSupport<T> of(Lookup lookup,String... fields){
var mh = createMh(lookup, fields);
return(obj)->{
            mh
.invokeExact(obj);
};
}
int hashCode(T obj);
}

and similar for EqualsSupport.

One usual issue with people hand writing equals and hashCode is that they doesn't play well together because there are not using the same set of fields,
grouping together the implementation of equals and hashCode alleviate this issue.


I feel like ObjectSupport is a bit nebulous and open ended and you'd inevitably end up needing more support methods such as a toString method.

yes, toString and perhaps compare, but i want to get right equals and hashCode first.

Rémi



To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-symp...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-symp...@googlegroups.com.

Steven Stewart-Gallus

unread,
May 16, 2019, 4:56:19 PM5/16/19
to mechanical-sympathy
Hi,

For a long while, I couldn't think of what to reply with.
I just don't feel the problem of grouping fields together deserves to distort the API that much.
I guess part of the problem is Java doesn't support a syntax for field references (even though VarHandleDesc are already implemented.)
It occurs to me that the hash and equals don't really need VarHandle references.
They really just need getters and don't actually need fields.

There is no reason you couldn't do a hash and equality on generated "properties."
Although you'd have to avoid boxing problems.

interface Getter<R, T> {
}
interface ObjectGetter<R, T> {
   
public T get(R record);

}
interface ValueGetter<R, T> extends Getter<R, T> {
}
interface IntGetter extends Getter<R, Integer> {
   
public int get(R record);
}

private static final Set<Getter<Point, ?>> GETTERS = Set.of((s) -> sx, (s) -> s.y);

private static final EqualsSupport<Point> EQUALS = EqualsSupport.of(GETTERS)
private static final HashsSupport<Point> HASHER = HashSupport.of(GETTERS);
private static final Stringer<Point> TOSTRING = Stringer.of(GETTERS);
// etc..

I think it might be possible to get working without explicit indy

static <R> EqualsSupport<R> makeEquals(Set<Getter<R, ?>> getters) {
   
EqualsSupport<O> equals = (a, b) -> true;
   
for (var getter : getters) {
     
if (getter instanceof ObjectGetter) {
         var g = (ObjectGetter<R, ?>) getter;
         
var oldEquals = equals;
         equals
= (a, b) -> oldEquals.apply(a, b) && Object.equals(g.get(a), g.get(b)));
     
} else if (getter instanceof IntGetter) {
         var g = (IntGetter<R>) getter;
        
var oldEquals = equals;
         equals
= (a, b) -> oldEquals.apply(a, b) && g.get(a) == g.get(b));
     
}
else {
         
// etc...
     
}
   
}
   
return equals;
}

I'm just not sure this would properly inline everything.

But this is starting to get into bikeshedding.

Thank you,
Steven

Steven Stewart-Gallus

unread,
May 23, 2019, 10:07:29 PM5/23/19
to mechanical-sympathy
Figured out something bizarre about combining lambdas just the other day.

Code like

static <R> EqualsSupport<R> makeEquals(Set<Getter<R, ?>> getters) {
   
EqualsSupport<O> equals = (a, b) -> true;
   
for (var getter : getters) {
     
if (getter instanceof ObjectGetter) {
         
var g = (ObjectGetter<R, ?>) getter;
         
var oldEquals = equals;
         equals
= (a, b) -> oldEquals.apply(a, b) && Object.equals(g.get(a), g.get(b)));
     
} else if (getter instanceof IntGetter) {
         var g = (IntGetter<R>) getter;
         
var oldEquals = equals;
         equals
= (a, b) -> oldEquals.apply(a, b) && g.get(a) == g.get(b));
     
}
 
else {
         
// etc...
     
}
   
}
   
return equals;
}

just doesn't work because the VM thinks it is recursive (it is the same method on the lambda just not the same lambda).

So what you need to is use MethodHandles combinators to patch the lambdas together. Oddly, for a sequence of lambdas I've found the loop one works well.
Reply all
Reply to author
Forward
0 new messages