Pattern matching for nested List with additional size constraints

Brian Goetz brian.goetz at oracle.com
Wed Jan 27 20:02:33 UTC 2021


Eventually, yes.

Your example wants to capture a number of conditions:
  - the Profile contains a non-null Rules
  - the Rules contains a non-null List<Rule>
  - The List<Rule> is non-empty
  - The first rule in the list is dynamic

Each of these conditions can be captured in a pattern, and the patterns 
can be composed.  Some of them can be handled by built-in patterns (type 
and deconstruction patterns).  Let me rebuild your hierarchy a little:

     record Profile(Rules rules) { }
     record Rules(List<Rule> rulesList) { }
     record Rule(DynamicConfig dc, StaticConfig sc) { }

and let's assume that records already have deconstruction patterns.  So 
we can do the easy parts already with these:

     if (p instanceof Profile(Rules(List<Rule> rulesList))
         && rulesList != null && rulesList.length() > 0
         && rulesList.get(0).dc() != null) { ... }

This means:
  - If p is a Profile, and non-null, cast to Profile, and extract the 
rules, and
  - If the rules is a Rules, and it is non-null, cast to a Rules, and 
extract the rulesList, and
  - do the rest imperatively

Obviously, this only helps a little bit.  What we need is a pattern that 
matches lists, and then can further match on their elements.  We have an 
existing varargs list factory List.of(T...); imagine we will have a 
varargs pattern that matches Lists and then matches nested patterns to 
the list elements, and imagine that's called List.of() also.  Then we 
can eliminate the next two tests:

     if (p instanceof Profile(Rules(List.of(var rule, ...)))
             && rule.dc() != null) {

         use(rule.dc());
     }

This means:
  - If p is a Profile, and non-null, cast to Profile, and extract the 
rules, and
  - If the rules is a Rules, and it is non-null, cast to a Rules, and 
extract the rulesList, and
  - If the rulesList is a non-null List, cast to a List, get the length, 
and it has a first element, get the first element, and bind the element 
to `rule`, and
  - do the rest imperatively

If you wanted to move all this into a pattern (say, so it could be used 
in a switch), you can move the last condition into a guard:

     if (p instanceof Profile(Rules(List.of(var rule, ...) & 
false(rule.dc() == null))) {
         use(rule.dc());
     }

In the future, when you can declare your own patterns, you can do 
better, by declaring a pattern in Rule that only matches rules with 
dynamic configuration, and yields up the dynamic configuration:

     if (p instanceof Profile(Rules(List.of(Rule.dynamic(var dc), ...))) {
         use(dc);
     }

So yes, we'll get there, but it will take a few iterations.


On 1/25/2021 5:58 PM, Swaranga Sarma wrote:
> Hello, I was going through some code in my work today and came across
> several similar looking cases and was wondering if pattern matching would
> be a more concise and cleaner way to pxress the logic.
>
> Here is a succinct class hierarchy that illustrates the example:
>
> class Profile {
>    Rules rules;
> }
> class Rules {
>    List<Rule> ruleList;
> }
> class Rule {
>    DynamicConfig dynamicConfig;
>    StaticConfig staticConfig;
> }
> class DynamicConfig {
>    SomeProperty property;
> }
> class StaticConfig {
>    SomeOtherProperty property;
> }
>
> With the above classes, we have a method "isDynamicConfigProfile" that
> determines if the Profile is a DynamicConfig profile. A Profile is
> considered dynamic if it contains a Rule with a non-null DynamicConfig
> member; also ALL the Rules of a profile can only contain one type either
> Dynamic or Static so checking just the first element in the ruleList is
> sufficient.
>
> The current code is implemented as:
>
> boolean isDynamicProfile (Profile profile) {
>    return profile.rules() != null &&
>               profile.rules().ruleList() != null &&
>               profile.rules().ruleList().size() > 0 &&
>               profile.rules().rulesList().get(0).dynamicConfig() != null;
> }
>
> Is the above method something that could be expressed with a switch and
> pattern matching; assuming these are converted to Record classes? Something
> like:
>
> boolean isDynamicProfile (Profile profile) {
>    return switch(profile) {
>      case Profile(Rules(Rule(DynamicConfig dc), Rule(var r2)...
> remainingElements)) -> true;
>      default-> false;
>    }
> }
>
> Is it even a valid usage of patterns? I think even the List interface would
> need to support pattern matching in this case.
>
> Regards
> Swaranga



More information about the amber-dev mailing list