-
Notifications
You must be signed in to change notification settings - Fork 66
Advanced usage
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
.
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.
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.
It's often useful to allow users to provide pattern modifiers - for
example map patterns allow an :only
modifier.