Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javaslang object decomposition not working

I am using Javaslang-2.1.0-alpha and its Javaslang-match equivalent to do some object decomposition. According to this by blog post by Daniel in the "Match the Fancy way" section:

Match(person).of( Case(Person("Carl", Address($(), $())), (street, number) -> ...) )

Should retrieve values matching the two wildcard patterns inside Address into street and number but the example does not even compile. I later realized all objects must be wrapped inside atomic patterns i.e. "Carl" becomes $("Carl"). This was after reading this issue.

I followed the updated tutorial but there was no update to this example.

I updated the example to this:

Person person = new Person("Carl", new Address("Milkyway", 42));

 String result2 = Match(person).of(
 Case(Person($("Carl"), Address($(),$())),
         (street, number) -> "Carl lives in " + street + " " + number),
 Case($(), () -> "not found")
 );
 System.out.println(result2);

It compiles but my values are not being matched properly, judging from the console output:

Carl lives in Carl Address [street=Milkyway, number=42]

It's clear that street contains Carl and number, the entire Address object.

When I try to add a third lambda parameter to catch Carl:

 Case(Person($("Carl"), Address($(),$())),
         (name, street, number) -> "Carl lives in " + street + " " + number)

The code can't compile, the lambda expression gets a red underline with the following error text:

The target type of this expression must be a functional interface

There is no way of ignoring a value with $_ in the latest versions of javaslang-match. So I want to match each of the atomic patterns which would return three lambda parameters as above.

I need somebody who understands this library to explain to me how to do this object decomposition in the latest version.

like image 718
egimaben Avatar asked Jan 11 '17 10:01

egimaben


1 Answers

Disclaimer: I'm the creator of Javaslang.

The case needs to handle (String, Address) -> {...}. The $() match arbitrary values but the handler/function receives only the first layer of the decomposed object tree. The $() are at the second layer.

Rule: All layers are matched against patterns, only the first layer is passed to the handler.

The first prototype of Match in fact handled arbitrary tree depths but methods hat to be generated under the hood for all possible combinations - max byte code size easily exceeded and compile time exponentially exploded to infinite.

The current version of Match is the only practical way in Java I see at the moment.

Update:

Please let me give a more figurative update on this topic.

We distinguish between

  1. The object graph of the input
  2. The pattern tree passed to the match case
  3. The decomposed objects of the object graph

Ad 1) The Object Graph

Given an object, the object graph is spanned by traversing the properties (resp. instance variables) of that object. Notably we do not prohibit that an object contains cycles (e.g. a mutable list that contains itself).

In Javaslang there is no natural way how to decompose an object into its parts. We need a so-called pattern for that purpose.

Example of an object graph:

     Person        <-- root
      /   \
 "Carl"  Address   <-- 1st level
          /   \
 "Milkyway"    42  <-- 2nd level

Ad 2) The Pattern Tree

A pattern (instance) inherently defines how to decompose an object.

In our example the pattern types look like this (simplified generics):

 Pattern2<Person, String, Address<String, Integer>>
               /           \
 Pattern0<String>  Pattern2<Address, String, Integer>
                          /   \
          Pattern0<String>     Pattern0<Integer>

The called pattern methods return instances of the above types:

      Person(...)
        /    \
 $("Carl")  Address(...)
             /   \
           $()    $()

Javaslang's Match API does the following:

  1. The Match instance passes the given person object to the first Case.
  2. The Case passes the person object to the pattern Person(...)
  3. The Person(...) pattern checks if the given object person is of type Person.
    • If true then the pattern decomposes the object into its parts (represented by a tuple) and checks if the sub-patterns $("Carl") and Address(...) match these parts (recursively repeats 3.)
    • If false, then Match passes the object to the next Case (see 2.)
    • If the pattern is atomic, i.e. it can't decompose the object any more, then equality is checked and the callers are informed all the way back to the match case.
  4. When a match case got a pattern match then it passes the decomposed objects of the first level of the object graph to the match case handler.

Currently Java's type system does not allow us to pass matched objects of arbitrary object graph/tree levels in a typed way to the handler.

Ad 3) Decomposed Objects

We already mentioned object decomposition above in 2). In particular it is used when parts of our given objects are send down the pattern tree.

Because of the limitation of the type system we mentioned above, we separate the process of matching an object from the process of handling decomposed parts.

Java allows us to match arbitrary object graphs. We are not limited to any level here.

However, when an object successfully matched, we can only pass the decomposed objects of the first layer to the handler.

In our example these decomposed objects are name and address of the given person (and not street and number).


I know that this is not obvious to the user of the Match API.

One of the next Java versions will contain value objects and native pattern matching! However, that version of pattern matching will be limited entirely to the first level.

Javaslang allows to match arbitrary object graphs - but it has a price. The handler does receive only the first layer of decomposed objects, which might be confusing.

I hope this answered the question in an understandable way.

- Daniel

like image 125
Daniel Dietrich Avatar answered Nov 09 '22 09:11

Daniel Dietrich