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

2

5

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.

Kolodgie answered 16/9, 2012 at 1:57 Comment(2)
Do you mean that you have (map? %) stored as data, i.e. as a list with two symbols?Comfrey
yes, I am really attempting to do this in a macro, I think I will quote everything to indicate what I want.Kolodgie
P
6

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)
Pandich answered 16/9, 2012 at 8:1 Comment(4)
...but why use the reader macro at all? why not just use fn directly?Pleadings
May be because he want the input predicates without fn signature and using %1 %2 and so on for parameter names. This question is very specific and doesn't provide the whole picture which may lead to a better solution overallPandich
good point, I hadn't seen that since his source data contains a reference to %, it seems designed to work with #(...).Pleadings
Wow, very interesting. I was sure I was missing something, as I could not figure out how to put the # in place using the normal macro mechanism. The reason I ask this is because I notice that the :post key in the prepost-args? in the defn argument list takes a vector of expressions. I was curious how the expressions were being evaluated, since they were being declared as expressions, and not as anonymous functions.Kolodgie
S
3

#(...) 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.
Seafaring answered 16/9, 2012 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.