- https://www.vavr.io/vavr-docs/#_pattern_matching
- https://static.javadoc.io/io.vavr/vavr/0.9.3/io/vavr/Predicates.html
- https://static.javadoc.io/io.vavr/vavr/0.9.3/io/vavr/Patterns.html
- in the workshop we will try to rewrite all methods from
java/workshops/Workshopusing pattern matching- don't forget to implement methods from
java/workshops/DecompositionWorkshop
- don't forget to implement methods from
- answers:
java/workshops/Answers
- easy example to set intuition
and a lazy equivalent with functions
int i = ... String s = Match(i).of( Case($(1), "one"), Case($(2), "two"), Case($(), "?") );int i = ... String s = Match(i).of( Case($(1), number -> "one"), Case($(2), number -> "two"), Case($(), number -> "?") ); - match is type-safe, so in above examples typing
will cause compilation errors
int i = ... String s = Match(i).of( Case($("1"), number -> "one"), // String instead of Integer Case($(new Object()), number -> "two") // Object instead of Integer ); switch-caseon steroids- saves us from writing piles of nested if-then-else
- is not natively supported in Java - we need third party libraries
- is natively supported in many languages -
Scala,Haskell,Kotlin
- is natively supported in many languages -
- more human readable way
- reduces the amount of code while focusing on the relevant parts
Match(i).ofis to some extent the equivalent ofswitchstatementCaseconsists of two parts:- part that matches logic from predicate, for example:
$(1) ~ (i -> i == 1) - part that tells what should be returned when value matches
- returned value can be any object
- returned value can be a supplier of an object
- returned value can be a function of a matched value
- part that matches logic from predicate, for example:
public interface Case<T, R> extends PartialFunction<T, R>- domain is defined by mentioned above predicate
- patterns overview
$()- wildcard pattern- saves us from a
MatchErrorwhich is thrown if no case matches
- saves us from a
$(value)- equals pattern$(predicate)- conditional pattern
- in most cases it acts like an expression, it results in a value, but there is a
possibility to handle side effects as well (well-known
Voidhack) - a constructor is a function which is applied to arguments and returns a new instance, a deconstructor is a function which takes an instance and returns the parts (object is unapplied)
- example of deconstruction -
LocalDate -> (year, month, day) - structural pattern matching allows us to deconstruct object hierarchies
return Match(date).of( Case($LocalDate($(2019), $(), $()), (year, month, day) -> ...), Case($LocalDate($(2018), $(), $()), ...), Case($LocalDate($(2017), $(), $()), ...) );
-
as we cannot perform exhaustiveness checks (feature not supporter on the language level), there is possibility to return an
Optionresult which prevents as fromMatchError:Match(obj).option( Case($(predicate1()), ...)); Case($(predicate2()), ...));- and in the case when there is no match for
predicate1()andpredicate2()we gotOption.none()instead ofMatchError
- and in the case when there is no match for
-
predefined
Predicatesmethod description allOf(Predicate<T>... predicates)A combinator that checks if all of the given predicates are satisfied. anyOf(Predicate<T>... predicates)A combinator that checks if at least one of the given predicates is satisfies. noneOf(Predicate<T>... predicates)A combinator that checks if none of the given predicates is satisfied. exists(Predicate<? super T> predicate)A combinator that checks if one or more elements of an Iterablesatisfy the predicate.forAll(Predicate<? super T> predicate)A combinator that checks if all elements of an Iterablesatisfy the predicate.instanceOf(Class<? extends T> type)Creates a Predicatethat tests, if an object is instance of the specified type.is(T value)Creates a Predicatethat tests, if an object is equal to the specified value usingObjects.equals(Object, Object)for comparison.isIn(T... values)Creates a Predicatethat tests, if an object is equal to at least one of the specified values usingObjects.equals(Object, Object)for comparison.isNotNull()Creates a Predicatethat tests, if an object is notnullisNull()Creates a Predicatethat tests, if an object isnull -
structural pattern matching (object decomposition using
Patterns)Try<Integer> _try = Try.of(() -> Integer.parseInt(number)); Match(_try).of( Case($Success($()), i -> ...), Case($Failure($()), ex -> ...) ); -
predefined
Patternsmethod purpose $Cons(API.Match.Pattern<_1,?> p1, API.Match.Pattern<_2,?> p2)List$Nil()List$Success(API.Match.Pattern<_1,?> p1)Try$Failure(API.Match.Pattern<_1,?> p1)Try$Future(API.Match.Pattern<_1,?> p1)Future$Invalid(API.Match.Pattern<_1,?> p1)Validation$Valid(API.Match.Pattern<_1,?> p1)Validation$Left(API.Match.Pattern<_1,?> p1)Either$Right(API.Match.Pattern<_1,?> p1)Either$Some(API.Match.Pattern<_1,?> p1)Option$None()Option$TupleN(N...)TupleN -
user-defined patterns
- generated by annotation (pre)processor and stored under
build/generated/sources/annotationProcessor(gradle) - implemented by us in
workshops.DecompositionAnswersstatic Tuple3<Integer, Integer, Integer> LocalDate(LocalDate date) { return Tuple.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth()); } - then generated by annotation processor
public static <_1 extends Integer, _2 extends Integer, _3 extends Integer> Pattern3<LocalDate, _1, _2, _3> $LocalDate(Pattern<_1, ?> p1, Pattern<_2, ?> p2, Pattern<_3, ?> p3) { return Pattern3.of(LocalDate.class, p1, p2, p3, workshops.DecompositionAnswers::LocalDate); } - and ready to be used
return Match(date).of( Case($LocalDate($(y -> y < 2000), $(), $()), return something for pre 2000), Case($LocalDate($(y -> y >= 2000), $(), $()), return something for after 2000) );
- generated by annotation (pre)processor and stored under
-
performing side effects
Match(PersonRepository.findById(id)).of( Case($None(), run(() -> display.push("cannot find person with id = " + id))), Case($Some($()), value -> run(() -> display.push("person: " + value + " processed"))) );runmust not be used as direct return value, i.e. outside of a lambda body - the cases have to be evaluated lazily