David Shaked
David Shaked

Reputation: 3381

Clojure pattern matching macro with variable arity that goes beyond explicit match cases

I'm in the process of translating some code from Scheme to Clojure. The Scheme code uses a macro called pmatch (https://github.com/webyrd/quines/blob/master/pmatch.scm) to pattern match arguments to output expressions. Specifically, it allows for variable capture as follows:

(define eval-expr
  (lambda (expr)
    (pmatch expr
      [(zero? ,e)
       (zero? (eval-expr e)))
...

In this use example, some input expression to eval-expr, '(zero? 0), should match the the first case. The car of the list matches to zero? and the arity of the input matches. As a consequence, 0 is bound to ,e and passed to (zero? (eval-expr e)), and this expr is evaluated recursively. In Haskell, which supports pattern matching natively, the code might translate to something like the following:

Prelude> let evalexpr "zero?" e = (e == 0) -- ignoring recursive application
Prelude> evalexpr "zero?" 0
True

In Clojure, I first tried to substitute pmatch with core.match (https://github.com/clojure/core.match), which was written by David Nolen and others, but, to my knowledge, this macro seems to

  1. only support a single arity of arguments per use
  2. only support explicit matching, rather than property based matching (available as guards)

Another option I'm trying is a lesser known macro called defun (https://github.com/killme2008/defun), which defines pattern matching functions. Here's an example:

(defun count-down
  ([0] (println "Reach zero!"))
  ([n] (println n)
     (recur (dec n))))

I'm still exploring defun to see if it gives me the flexibility I need. Meanwhile, does anyone have suggestions of how to pattern match in Clojure with 1. flexible arity 2. variable capture?

Upvotes: 1

Views: 408

Answers (1)

Yuri Steinschreiber
Yuri Steinschreiber

Reputation: 2698

Ignoring recursive application:

(ns test.test
  (:require [clojure.core.match :refer [match]]))


(def v [:x 0])

(def w [:x :y 0])

(defn try-match [x]
  (match x
         [:x e] e
         [:x expr e] [expr e]
         ))

(try-match v)
;; => 0

(try-match w)
;; => [:y 0]


;; Matching on lists (actually, any sequences)

(defn try-match-2 [exp]
  (match exp
         ([op x] :seq) [op x]
         ([op x y] :seq) [op x y]))

(try-match-2 '(+ 3))
;; => [+ 3]

(try-match-2 '(+ 1 2))
;; => [+ 1 2]

See https://github.com/clojure/core.match/wiki/Overview for more details.

Additionally, I suggest you have a close look at Clojure destructuring. Lots of things can be done with it without resorting to core.match, actually your use case is covered.

Upvotes: 3

Related Questions