-
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 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
.
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.