Skip to content
David Nolen edited this page Jun 18, 2013 · 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 can not be use to extend types you don't control.

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.

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

Note 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

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. core.match simply needs some way to know whether the two same patterns in two different clauses may be tested together. For example above the pattern #"hello" appears twice 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 pattern patterns may be tested together. Two instances of the same logical RegexPattern are not equal, this groupable? 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

Clone this wiki locally