Problem with passing a vector as a binding to the for macro

后端 未结 4 1050
野性不改
野性不改 2021-02-20 06:13

I have an arbitrary number of lists which I would like to process using the for macro. I want to create a function that passes a vector as the binding since the number of lists

相关标签:
4条回答
  • 2021-02-20 06:27

    The key is that for is a macro. At macro-expansion time, testvector is a symbol. It will evaluate to a vector at evaluation time, but it's not a vector from the perspective of the for macro.

    user=> (defmacro tst [v] (vector? v))
    #'user/tst
    user=> (tst testvector)
    false
    user=> (vector? testvector)
    true
    user=> (defmacro tst2 [v] `(vector? ~v))
    #'user/tst2
    user=> (tst2 testvector)
    true
    

    If you check the source for the for macro (in core.clj), you'll see that for uses an unquoted vector? call, just like tst in the example above.

    0 讨论(0)
  • 2021-02-20 06:35

    Although not a solution to your problem, it should be noted that what you are doing can more easily be achieved with map rather than for e.g.

    user=> (def list1 '("pink" "green"))
    #'user/list1
    user=> (def list2 '("dog" "cat"))
    #'user/list2
    user=> (map #(str %1 "-" %2) list1 list2)
    ("pink-dog" "green-cat")
    user=> 
    

    Another useful technique when learning and experimenting is to use keywords rather than strings. This can reduce typing i.e. no need to put the values in quotes and can sometimes help identify errors more easily. Instead of (def list1 '("pink" "green")) you can just do (def list1 '(:pink :green)). Even better, rather than using lists, try using vectors and then you don't have to quote it (saving another keystroke).

    0 讨论(0)
  • 2021-02-20 06:37

    You can try to force the evaluation of the binding vector. Instead of trying to define a macro that will wrap the for macro, wrap it in a function, e.g.

    (defn for-fn [bindings expr]
      (eval `(for ~bindings ~expr))) 
    

    Then you can actually build a binding vector with a few additional constraints since all s-expressions inside the binding vector need to be valid and contain a verb as the first element.

    (let [bindings '[a (list 1 2) b (list 3 4) c (range 10 12)
                     :when (> (+ a b c) 15)]
          expr '(str a "-" b "-" c)]
      (for-fn bindings expr)) 
    

    And with your example :

    (def list1 '("pink" "green"))
    (def list2 '("dog" "cat"))
    (def testvector (vector 'A (cons 'list  list1) 'B (cons 'list list2)))
    
    (for-fn testvector '(str A "-" B))
    => ("pink-dog" "pink-cat" "green-dog" "green-cat")
    

    Note : since for-fn is function, you need to quote the expression (str A "-" B) to prevent an early evaluation (before A & B are bound).

    0 讨论(0)
  • 2021-02-20 06:40

    Here is a method of last resort. Be warned, wherever you see read-string that is code for Here Be Dragons! (Due to security risks, and lack of compile-time consistency guarantees about the behaviour of your code)

    (def list1 '("pink" "green"))
    (def list2 '("dog" "cat"))
    (for [A list1 B list2] (str A "-" B))
    
    (def testvector (vec (list 'A list1 'B list2)))
    
    (def testvector-vec (vec (list 'A (vec list1) 'B (vec list2))))
    
    (def for-string (str "(for " testvector-vec "(str A \"-\" B))"))
    
    (eval (read-string for-string))
    > ("pink-dog" "pink-cat" "green-dog" "green-cat")
    
    0 讨论(0)
提交回复
热议问题