A simple clojure library for pattern matching.
problem: With most pattern matching libraries, patterns do not look like real clojure data structures. This makes it difficult to write patterns that match to clojure expressions. With this library, your patterns look like clojure code and data structures with minimal modifications.
Patterns will be simplified and compiled to clojure code at compile time. Using this library, it is also possible to create multimethods with patterns as dispatch values.
- Leiningen dependency:
[erdos.match "0.1.0-SNAPSHOT"]
- Require the namespace:
(require '[erdos.match :refer (match)])
The (match)
macro is used for pattern matching. The first argument is the value to be matched. The following arguments are patterns and bodies. Only the branch for the first matching pattern will be executed. When no pattern is matched, nil value is returned.
example
(match expression-to-match
pattern-1 branch-1
pattern-2 branch-2
;...
pattern-n branch-n)
Numbers, string, symbols and keywords are supported.
example
(match a
:first :keyword
second :symbol
"third" :string
4 :number)
;; a=4 => :number, a='second => :symbol, etc..
The []
notation is for matching vectors and ()
is for matching lists and seqs. They are not interchangeable. The &
sign is used for matching the remaining part of the vector or list.
example
(match a
[] :empty-vect
() :empty-list
[_] :vec1
[& _] :vect-many
(_) :list-1
(& _) :list-many)
;; a=[] => :empty-vect, a=(3) => :vec1, etc..
Values in maps can be matched by keys. For example, pattern {:a ?a}
matches {:a 1}
object by binding value 1 to var ?a.
example
(match a
{:a ?a, :b ?b} (str "a=" ?a " b=" ?b)
{:a _} "only :a is given"
{:b _} "only :b is given"
{} "neither :a nor :b given"
_ "it is not even a map.")
The _
symbol can be used to match any object and null values. Symbols starting with ?
sign will be used as var names for capturing objects. (They can also be used for type matching, see examples.) The first occurence of the symbol is used for binding and all other occurences are for equality checking. Thus, you can check for repeating parts in your pattern.
You can add type matching using the :tag
meta info on var names. For example, ^Integer ?i
matches an Integer value and binds it to var ?i
.
example
(match "what is this?"
^Integer ?a :int
^String ?a :string
^Long ?a :long
_ :unknown)
;; => :string
You can also add guard functions using the :guard
meta map key. For example, the pattern ^{:guard even?} ?i
will match even numbers. Please note, you can not use meta info on the _
symbol. All other symbols will be handled as concrete objects for matching.
example
Match even numbers.
(match 24
^{:guard even?} ?e :even
_ :odd)
;; => :even
There is a support for the notation of the most commonly used guards. Symbols of the following form: ?fun/varname
will be resolved to ?varname
with the guard function corresponding to fun
. For the implemented guard functions, please see erdos.match/symbol-prefix-guards
.
example
(defmatcher m)
(addmatch m ?str/_ "That is a string!")
(addmatch m ?num/n (str "Your number is: " n))
(m "asdf") ;=> That is a string!
(m 23) ;=> Your number is 23
For optionally matching for items in lists and sequences, use the :when
meta key with a function value. The :guard
meta key will be ignored in this case.
example
(match '(1 2 3)
(1 ^{:when string? } ?s 2 3) true)
;; => true
(match '(1 "oneandhalf" 2 3)
(1 ^{:when string? } ?s 2 3) ?s)
;; => "oneandhalf"
It is also possible to match for expressions calculated at compile time or at run time. For compile-time matching, use the ~
prefix. For example: ~true
matches for the value true, whereas true
will match for the symbol 'true. For run-time matching, use the @
prefix. You can also use the names of already matched var names in this expression.
Do you want to match for expressions computed in compile time?
(match 10
~(+ 1 2 3) :first
~(+ 1 2 3 4) :second
_ :unknown)
;; => :second
Or the other way: match for expressions computed when matched for. Therefore, you can match for already matched variables.
(match [true false]
[?a @(not ?a)] :not-the-same
_ :the-same)
;; => :not-the-same
Matcher methods are very similar to multimethods. First, you declare a matcher function with defmatcher
. Second, you add match cases to the matcher using addmatch
.
(defmatcher m)
declares a new matcher named m
.
(addmatch m pat body)
defines a matching case for matcher named m
.
Match the vector by the number of elements
(match a
[] 0,
[_] 1,
[_ _] 2,
[_ _ _] 3,
[& _] :many)
;; when a=[] => 0, a=[5] => 1, a=[4 5] => 2, etc..
Logical OR operator
(match a
[~nil ?a] ?a,
[~false ?a] ?a,
[?a _] ?a)
You can also refer to previously matched values.
(match a
[?a ?a] (str ?a "=" ?a)
[?a ?b] (str ?a "/=" ?b))
;; when a=[1 1] => "1=1", a=[1 2]=>"1/=2", etc..
Checking for symbols and lists is possible. Please note the different syntax for lists and vectors.
(match a
(if ?cond ?then ?else) (str "if-expression")
(when ?cond & ?then) (str "when-expr")
_ :unexpected)
To check out the compiled pattern, use the match-pattern
function or the match-pattern*
macro.
For example, the following code..
(match-pattern
"asd"
^Integer ?i (str ?i "is and int")
^String ?s (str ?s "is a string")
^Long ?l (str ?l "is a long"))
..will compile to something like this:
(clojure.core/first
(clojure.core/let [MC23818 "asd"]
(clojure.core/or
(if (clojure.core/instance? Integer MC23818)
(clojure.core/let [?i MC23818]
[(str ?i "is and int")]))
(if (clojure.core/instance? String MC23818)
(clojure.core/let [?s MC23818]
[(str ?s "is a string")]))
(if (clojure.core/instance? Long MC23818)
(clojure.core/let [?l MC23818]
[(str ?l "is a long")])))))
The MIT License
Copyright © 2014 Janos Erdos
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.