[External] : Re: Proposal: java.lang.runtime.Carrier

forax at univ-mlv.fr forax at univ-mlv.fr
Sat Mar 5 22:54:14 UTC 2022


> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "Jim Laskey" <james.laskey at oracle.com>, "amber-spec-experts"
> <amber-spec-experts at openjdk.java.net>
> Sent: Friday, March 4, 2022 3:11:44 AM
> Subject: Re: [External] : Re: Proposal: java.lang.runtime.Carrier

>>> Either way, we don't need to mutate or replace carriers.
>> You want the same carrier for the whole pattern matching:

> I think you're going about this backwards. You seem to have a clear picture of
> how pattern matching "should" be translated. If so, you should share! Maybe
> your way is better. But you keep making statements like "we need" and "we want"
> without explaining why.

>> - if you have a logical OR between patterns (not something in the current Java
>> spec but Python, C# or clojure core.match have it so we may want to add an OR
>> in the future)

> OR combinators are a good point, but they can be done without a with operation.

>> - if different cases starts with the same prefix of patterns, so you don't have
>> to re-execute the de-constructors/pattern methods of the prefix several times

> Agree that optimizing away multiple invocations is good, but again, I don't see
> that as being coupled to the pseudo-mutability of the carrier.

> Perhaps you should start with how you see translation working?
Sure, 
the idea is that to execute the pattern matching at runtime, each step is decomposed into few higher order functions, things like testing, projecting a value (deconstructing), etc 
each higher order manipulate one kind of function that takes two values, the value we are actually matching and the carrier, and returns a carrier. 

Obviously, each simple function is a method handle, so there is no boxing in the middle and everything is inlined. 

Here is a possible decomposition 
- MH of(Object carrier, MH pattern) 
which is equivalent to o -> pattern.apply(o, carrier) 
- MH match(int index) 
which is equivalent to (o, carrier) -> with(index, carrier, 0), i.e. return a new carrier with the component 0 updated with index 
- MH do_not_match() 
which is equivalent to match(-1) 
- MH is_instance(Class type) 
which is equivalent to (o, carrier) -> type.isInstance(o) 
- MH is_null() 
which is equivalent to (o, carrier) -> o == null 
- MH throw_NPE(String message) 
which is equivalent to (o, carrier) -> throw new NPE(message) 
- MH project(MH project, MH pattern) 
which is equivalent to (o, carrier) -> pattern.apply(project.apply(o), carrier) 
- MH bind(int binding, MH pattern) 
which is equivalent to (o, carrier) -> pattern.apply(with(o, carrier, binding) 
- MH test(MH test, MH target, MH fallback) 
which is equivalent to (o, carrier) -> test.test(o, carrier)? target.apply(o, carrier): fallback.apply(o, carrier) 
- MH or(MH pattern1, MH pattern2) 
which is equivalent to 
(o, carrier) -> { 
var carrier2 = pattern1.apply(o, carrier); 
if (carrier2.accessor[0] == -1) { 
return carrier2; 
} 
return pattern2.apply(o, carrier2); 
} 

For the carrier, the convention is that the component 0 is an int, -1 means "not match", and any positive index means the indexth case match. 

In the detail, it's a little more complex because we sometimes need to pass the type of the first parameter to correctly type the returned MH and we also need an object CarrierMetadata that keep track of the type of the carrier components (and provides an empty carrier and the accessors/withers). 

Here is a small example 

record Point( int x, int y) {} 
record Rectangle(Point p1, Point p2) {} 

// Object o = ... 
//switch(o) { 
//  case Rectangle(Point p1, Point p2) -> ... 
//} 

var lookup = MethodHandles. lookup (); 

var carrierMetadata = new CarrierMetadata( methodType (Object. class , int . class , Point. class , Point. class )); 
var empty = carrierMetadata.empty(); 

var op = of (empty, 
test ( is_instance (Object. class , Rectangle. class ), 
cast (Object. class , 
or (carrierMetadata, 
project ( record_accessor (lookup, Rectangle. class , 0 ), 
test ( is_null (Point. class ), 
do_not_match (Point. class , carrierMetadata), 
bind ( 1 , carrierMetadata))), 
project ( record_accessor (lookup, Rectangle. class , 1 ), 
test ( is_null (Point. class ), 
do_not_match (Point. class , carrierMetadata), 
bind ( 2 , carrierMetadata, 
match (Point. class , carrierMetadata, 0 )))) 
) 
), 
throw_NPE (Object. class , "o is null" ) 
) 
); 

// match: new Rectangle(new Point(1, 2), new Point(3, 4)) 
var rectangle1 = (Object) new Rectangle( new Point( 1 , 2 ), new Point( 3 , 4 )); 
var carrier1 = op.invokeExact(rectangle1); 
System. out .println( "result: " + ( int ) carrierMetadata.accessor( 0 ).invokeExact(carrier1)); 
System. out .println( "binding 1 " + (Point) carrierMetadata.accessor( 1 ).invokeExact(carrier1)); 
System. out .println( "binding 2 " + (Point) carrierMetadata.accessor( 2 ).invokeExact(carrier1)); 

// match: new Rectangle(new Point(1, 2), null) 
var rectangle2 = (Object) new Rectangle( new Point( 1 , 2 ), null ); 
var carrier2 = op.invokeExact(rectangle2); 
System. out .println( "result: " + ( int ) carrierMetadata.accessor( 0 ).invokeExact(carrier2)); 
System. out .println( "binding 1 " + (Point) carrierMetadata.accessor( 1 ).invokeExact(carrier2)); 
System. out .println( "binding 2 " + (Point) carrierMetadata.accessor( 2 ).invokeExact(carrier2)); 

The full code is available here: 
[ https://github.com/forax/switch-carrier/blob/master/src/main/java/com/github/forax/carrier/java/lang/runtime/Patterns.java | https://github.com/forax/switch-carrier/blob/master/src/main/java/com/github/forax/carrier/java/lang/runtime/Patterns.java ] 

I believe, using a function with two parameters, the actual value we are switching upon and the carrier that will gather the bindings is better than using only a carrier as parameter because in that case, you need to use the carrier to store all the intermediary objects even if they are not kept as bindings. 

Rémi 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20220305/5579001f/attachment-0001.htm>


More information about the amber-spec-experts mailing list