Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you convert a expression into a predicate? (Clojure)

Given that I have a expression of the form

'(map? %)

How do I convert it into something like

'#(map? %)

So that I can ultimately expand it into something like

'(apply #(map? %) value)

I think I should use a macro in some way, but am not sure how.

like image 611
Stephen Cagle Avatar asked Sep 16 '12 01:09

Stephen Cagle


2 Answers

The # invokes a reader macro and reader macros expansion happen before normal macros expansion happens. So to do what you have mentioned, you need to go through the reader in your macro using read-string as shown below.

(defmacro pred [p v] 
     (let [s# (str \# (last p))] 
         `(apply ~(read-string s#) ~v)))



user=> (pred '(map? %) [{}])
true
user=> (pred '(map? %) [[]])
false

In case the data i.e the predicate expression is available at runtime then you need to use a function (which is more flexible then macro).

(defn pred [p v] 
   (let [s (read-string (str \# p))] 
      (eval `(apply ~s ~v))))

user=> (map #(pred % [12]) ['(map? %)'(even? %)])
(false true)
like image 51
Ankur Avatar answered Nov 01 '22 20:11

Ankur


#(...) is a reader macro. I don't think that you can generate expression with reader macro. For example '#(map? %) will automatically expand into (fn* [p1__352#] (map? p1__352#)) or something similar.

Here's a somewhat relevant discussion on other reader macro.

Would it be possible to change format of the predicate? If it looked something like:

'([arg1] (map? arg1))

Then it would be trivial to make a function form it:

(cons 'fn '([arg1] (map? arg1)))

(def pred (eval (cons 'fn '([p](map? p)))))
#'predicate.core/pred

(pred {})
true

(pred 10)
false

Now please don't hate me for what I'm going to post next. I wrote an overly simplified version of the function reader macro:

(defn get-args [p]
  (filter #(.matches (str %) "%\\d*")
          (flatten p)))

(defn gen-args [p]
  (into [] 
        (into (sorted-set) 
              (get-args p))))

(defmacro simulate-reader [p]
  (let [arglist (gen-args p)
        p (if (= (str (first p)) "quote")
            (second p)
            p)]
    (list 'fn (gen-args p) p)))

Using it is very straight-forward:

((simulate-reader '(map? %)) {}) ; -> true
; or without quote
((simulate-reader (map? %)) {})

; This also works:
((simulate-reader '(+ %1 %2)) 10 5) ; -> 15

The difference with the other solution given by @Ankur is:

  1. I like mine less. I just thought it was a fun thing to do.
  2. Does not require conversion to string and then applying reader macro to it.
like image 3
Ivan Koblik Avatar answered Nov 01 '22 20:11

Ivan Koblik