Advanced usage

Josh Freckleton edited this page Jun 24, 2016 · 16 revisions

Participating in pattern matching

core.match promotes matching abstractions, not concrete types. Sequence matching will work on any Sequential type. Map matching will work on any instance of ILookup.

However how can we make pattern matching work on types that we don't control like java.util.Date? Since clojure.lang.ILookup is a Java interface and not a protocol this cannot be used to extend types you don't control (this doesn't apply to ClojureScript where ILookup is a protocol).

In order to address this shortcoming core.match provides map matching on any type extended to the clojure.core.match.protocols/IMatchLookup protocol.

(extend-type java.util.Date
  clojure.core.match.protocols/IMatchLookup
  (val-at [this k not-found]
    (case k
      :day (.getDay this)
      :month (.getMonth this)
      :year (.getYear this)
      not-found)))

This is all you need to allow map pattern matching on all java.util.Date instances.

(matchm [(java.util.Date. 2010 10 1 12 30)]
   [{:year 2009 :month a}] a
   [{:year (:or 2010 2011) :month b}] b
   :else :no-match)

Note that there is no reason to actually extend java.util.Date in this way, you can get this functionality for free by requiring clojure.core.match.date.

Extending pattern matching

Notice: this example is out of date. Please see the regex example in the tests

Participating in pattern matching is extremely powerful but some use cases may require that you need to actually extend the pattern matching language itself. Imagine that you wanted to allow regular expressions in your patterns.

(match [x y]
  [#"hello"   :foo] :a0
  [#"hello"   _   ] :a1
  [#"goodbye" _   ] :a2
  :else :no-match)

When core.match compiles a match expression it calls emit-pattern on all the elements that appear in the clauses. By defining how java.util.regex.Pattern should be handled you can make the above work.

(defrecord RegexPattern [regex])

(defmethod emit-pattern java.util.regex.Pattern
  [pat]
  (RegexPattern. pat))

Now when java.util.regex.Pattern is encountered it will emit an instance of your RegexPattern. The only thing left to do is define exactly what kind of test your pattern produces to check for success or failure. You just need to implement the to-source multimethod for your pattern.

(defmethod to-source RegexPattern
  [pat ocr]
  `(re-find ~(:regex pat) ~ocr))

That's it!

One additional thing you can do is allow core.match to optimize testing of your patterns. In order to optimize core.match needs to know that adjacent patterns in the same column may be tested together. In the pattern above #"hello" appears stacked in the first column - these cases could be tested together (core.match will not attempt to share these by default as making assumptions about equality is risky business).

This information can be communicated to the core.match compiler by implementing groupable?

(defmethod groupable? [RegexPattern RegexPattern]
  [a b]
  (let [^Pattern ra (:regex a)
        ^Pattern rb (:regex b)]
    (and (= (.pattern ra) (.pattern rb))
         (= (.flags ra) (.flags rb)))))

You can see that this logic defines whether two patterns may be tested together. Two instances of the same logical RegexPattern are not equal, this groupable? implementation provides the information that core.match needs to know that two instances are in fact the same.

There is a simple implementation of this present in core.match in the clojure.core.match.regex namespace.

Implementing a new pattern

The most sophisticated extension to core.match is constructing a completely new pattern. The previous example is a greatly simplified interface to more powerful facilities - the regular expression example does allow for arbitrarily nested patterns the way that other patterns do.

Writing your own pattern that supports arbitrary nesting requires understanding the algorithm that core.match employs when compiling a pattern. You should read Understanding the algorithm before reading the rest of this section.

Pattern modifiers

It's often useful to allow users to provide pattern modifiers - for example map patterns allow an :only modifier.