Pattern Matching Starter
Update: This post describes a prototype of the Match API which has been tested pre-2.0.0. With Javaslang 2.0.0 final we introduced a revised version of structural pattern matching. Read more about it here.
Structural pattern matching is an awesome feature (often confused with pattern matching using regular expressions). It allows us to deconstruct object hierarchies.
However, Java has no structural pattern matching which makes working with object hierarchies cumbersome. Javaslang adds this feature to Java.
(The examples of this post can be found on github)
Types
Such a type hierarchy could look like this (simplified):
class Person {
String name;
Address address;
}
class Address {
String street;
int number;
}
Let's say we have an instance of Person and want to read the Address details, if the name of the Person is 'Carl'. Here is our example setup:
Person person = ...;
The good old Java way
This is how we would do it in plain Java:
if (person != null && "Carl".equals(person.getName())) {
Address address = person.getAddress();
if (address != null) {
String street = address.getStreet();
int number = address.getNumber();
...
}
}
}
Match the fancy way
This is how we do it using structural pattern matching in Javaslang:
Match(person).of(
Case(Person("Carl", Address($(), $())), (street, number) -> ...)
)
Currently the number of decomposed objects is limited to 8 (but could be increased easily).
There are three atomic matchers:
- $_ matches any value but does not extract it
- $() matches any value and extracts it as lambda argument
- $(value) matches a specific value and extracts it as lambda argument
Beside these, we define composite matchers to decompose / unapply types.
Decomposition with Unapply
In order to be able to decompose or unapply objects, we need to define so-called unapply patterns, which can be used with Match:
@Patterns
class Starter {
@Unapply
static Tuple2<String, Address> Person(Person person) {
return Tuple.of(person.getName(), person.getAddress());
}
@Unapply
static Tuple2<String, Integer> Address(Address address) {
return Tuple.of(address.getStreet(), address.getNumber());
}
@Unapply
static Tuple3<Integer, Integer, Integer> LocalDate(
LocalDate date) {
return Tuple.of(
date.getYear(),
date.getMonthValue(),
date.getDayOfMonth()
);
}
}
It is ideomatic to name the unapply-method like the object to be unapplied.
When the project is compiled, the Patterns are automatically generated by Javaslang's annotation processor and stored in target/generated-sources/annotations
.
Our patterns are called StarterPatterns
. We need to make our project aware of these new sources. We could either add this directory in our IDE as source directory or use a Maven or Gradle plugin to do so (more information on that).
As we see, we may also match existing JDK or user-defined classes:
Match(localDate).of(
Case(LocalDate(2016, 2, 13), () -> "2016-02-13"),
Case(LocalDate(2016, $(), $_), m -> "month " + m + " in 2016"),
Case(LocalDate($(2016), $(), $_), (y, m) ->
"month " + m + " in " + y
),
Case($_, () -> "(catch all)")
);
Please note, that Match is type-safe! For example this would lead to a compile error:
// year is not a String!
LocalDate($("2016"), $_, $_)
In our current development we integrate the new Match feature more tightly into Javaslang. For example Case
is a Function<Object, Option<R>>
and could be used to select objects:
List<Person> persons = ...
List<String> strings = persons.collect(
Case(Person($(), Address($_, $())), (name, number) ->
name + ", " + number
)
);
Also more syntactic sugar would be nice:
List<String> names = persons.match(
Case(Person($(), $_), name -> name)
);
I hope you enjoyed it. The match API is still in a prototypic state. But the first results are really useful.
- Daniel