Class Pattern<T>

java.lang.Object
de.unruh.javapatterns.Pattern<T>
Type Parameters:
T - The type of the value that is pattern-matched
Direct Known Subclasses:
Capture, Patterns.Instance

public abstract class Pattern<T>
extends java.lang.Object
A pattern that matches a value and assigns values to capture variables (Capture). When implementing a pattern, the central method is apply which takes a value value (and a MatchManager mgr) and performs the actual pattern matching. Inside apply, we can perform the following operations:
  • It can analyze and decompose value (or compute arbitrary derived values).
  • It can reject value by invoking reject().
  • It can invoke the apply function of other patterns on the values obtained by analyzing value. (Typically, those would be given as arguments when constructing this pattern.) If any of the subpatterns fail, the this pattern fails, too (unless this is caught via a protected block (see below).
  • It can invoke mgr.protectedBlock to execute a protected block of actions (see below).

Some important notes:

  • There is no explicit mechanism for assigning capture variables. However, capture variables are patterns themselves, and thus can be passed to a pattern as subpatterns. If x is a capture, then x.apply(...) will have the effect of assigning that capture.
  • Failure is marked via an exception PatternMatchReject (thrown by reject()}. This exception should never be caught because subpatterns might have assigned values to some capture variables already and it would be undefined which capture variables would be assigned and which not. Instead, if we want to apply a subpattern but not fail if the subpattern fails, we need to execute the pattern in a protected block by invoking mgr.protectedBlock}(...). This protected block then returns false if the subpattern(s) in ... fail.
  • When invoking patterns that were passed as arguments when constructing this pattern, it is recommended to do so in the order were given. This is to ensure that captures are assigned in the right order. For example, if two patterns p1 and p2 are given, and p1 assigns a capture x, then p2 may already read its assigned value. (E.g., Array(x, Is(x)) will work but Array(Is(x), x) will not.) If the patterns are invoked in a different order, this should be documented clearly.
  • If a patten fails, it does not matter at which point it does it. E.g., "subpattern.apply(...); if (...) reject()" and "if (...) reject(); subpattern.apply(...)" are equivalent. (Unless the subpattern has any additional side-effects besides assigning captures.) All captures that were assigned by this pattern or subpatterns will be reset upon failure.
  • Patterns should not have any side-effects.
  • When taking subpatterns as arguments, those should have types of the form Pattern<? super X> for some type X, not Pattern<X>. (This means, Pattern should be treated as a contravariant type constructor.)

Example 1: As an example for designing a pattern, consider the following use case. Assume a class Pair<X,Y> with two members X first and Y second. We want to construct a pattern for this class. For this, we create a static method (by convention, of the same name as Pair) that returns an anonymous subclass of Pattern<Pair<X,Y>>:

static <X, Y> Pattern<Pair<X, Y>> Pair(Pattern<? super X> firstPattern, Pattern<? super Y> secondPattern) {
    return new Pattern<>() {
        public void apply(MatchManager mgr, Pair<X, Y> value) throws PatternMatchReject {
             if (value == null) reject();
             firstPattern.apply(mgr, value.first);
             secondPattern.apply(mgr, value.second);
         }

         public String toString() {
             return "Pair(" + firstPattern + "," + secondPattern + ")";
         }
     };
 }
 
Most of this is boilerplate. The crucial part are the three lines in the definition of apply: if (value == null) reject(); rejects the matched value if it is null. And firstPattern.apply(mgr, value.first); matches the first component of the matched value using the subpattern subpattern. And analogously for the next line and the second component.

The full example can be found in PatternDocumentationTest.java, test case example1.

Example 2: As an example for a pattern that does not correspond to an existing class such as Pair, we consider the following use case. We want a pattern that matches strings of the form "firstname lastname" (and allows us to apply further subpatterns to the first and last name). The following method achieves this:

static Pattern<String> FullName(Pattern<String> firstNamePattern, Pattern<String> lastNamePattern) {
    return new Pattern<>() {
         public void apply(MatchManager mgr, String value) throws PatternMatchReject {
             if (value == null) reject();
             String[] parts = value.split(" ");
             if (parts.length != 2) reject();
             firstNamePattern.apply(mgr, parts[0]);
             lastNamePattern.apply(mgr, parts[1]);
         }

         public String toString() {
             return "FullName(" + firstNamePattern + "," + lastNamePattern + ")";
         }
     };
 }
 
Here the apply method checks whether the matched value is non-null and consists of two words (we do not handle names with more than two components) and rejects otherwise. Then it applies the two subpatterns to the first and second word of the string, respectively.

The full example can be found in PatternDocumentationTest.java, test case example2.

  • Constructor Summary

    Constructors 
    Constructor Description
    Pattern()  
  • Method Summary

    Modifier and Type Method Description
    abstract void apply​(@NotNull MatchManager mgr, T value)
    Performs the pattern match.
    static <T> Capture<T> capture​(@NotNull java.lang.String name)
    Creates a new capture variable.
    static void reject()
    Should be called inside apply(de.unruh.javapatterns.MatchManager, T) to indicate that the pattern did not match.
    abstract java.lang.String toString()
    Should give a human readable representation of this pattern.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Constructor Details

  • Method Details

    • apply

      public abstract void apply​(@NotNull @NotNull MatchManager mgr, @Nullable T value) throws PatternMatchReject
      Performs the pattern match. See the class documentation.

      Must only be invoked from withing an apply. (I.e., a pattern may invoke apply on its subpatterns from within its own apply method.)

      Parameters:
      mgr - the MatchManager that manages the life-cycle of the captures in this pattern match. Must be passed to subpatterns and not be kept after the termination of the invocation of apply(...).
      value - the value to be pattern-matched
      Throws:
      PatternMatchReject - to indicate that value did not match the pattern. Should be thrown only by calling reject()}.
    • toString

      @Contract(pure=true) public abstract java.lang.String toString()
      Should give a human readable representation of this pattern. Used in error messages.
      Overrides:
      toString in class java.lang.Object
    • reject

      @Contract(pure=true, value="-> fail") public static void reject() throws PatternMatchReject
      Should be called inside apply(de.unruh.javapatterns.MatchManager, T) to indicate that the pattern did not match.

      Can also be invoked inside a match action (the code that is executed if a pattern did match) to declare the match as failed. If that happens, the pattern match (via Match.match(In, de.unruh.javapatterns.Case<In, Return, Exn>...)) will continue with the next pattern.

      Throws:
      PatternMatchReject - always thrown
    • capture

      @Contract(pure=true, value="_ -> new") public static <T> Capture<T> capture​(@NotNull @NotNull java.lang.String name)
      Creates a new capture variable.
      Parameters:
      name - Name of the capture. Used only for informative purposes (printing patterns, error messages). It is recommended that this is the name of the variable holds this capture.
      Returns:
      the capture variable