Clojure宏:根据条件将过滤后的地图和Arity传递给其他宏

时间:2019-02-22 12:06:45

标签: clojure

(defmacro action1 [] `(prn "action1" ~'start ~'etype1))

(defmacro block [bindings & body] 
  `(let [~@(mapcat (fn [[k v]] [(if (symbol? k) k (symbol (name k))) `'~v]) (cond
                     (map? bindings) bindings
                     (symbol? bindings) (var-get (resolve bindings))
                     :else (throw (Exception. "bindings must be map or symbol"))))]
       ~body))

(defmacro bar [ctx arity & expr]
      `(let [~'t1 "lv" ~'np (prn "bar_1st_let" '~ctx ~ctx '~arity ~arity '~arity(resolve (first '~arity)) )
            ] 
            (block ~ctx ;;~ctx = {start "s1" top "x"}
              (fn '~arity ;; '~arity = [etype1 cid1 id1 pl1]
                (let [~'__execonceresult 1]
                  (do ~@expr)     
                )
              )
            )
        )
  )

(defmacro foo_multi [metadata ctxv aritym & expr]
  `(let [~@(mapcat (fn [[k v]] [k `~v]) metadata) ~'np (prn "foo_multi_1st_let" '~aritym)] 
  (fn ~aritym 
      (for [~'ctx (filter #(= (% (some (fn [~'m] (if (= (name ~'m) "top") ~'m)) (keys %))) ~'etype) '~ctxv)]
        (do (prn "foo_multi_b4_case" ~'ctx ~'etype ~aritym)
        (case ~'etype
        "x"
          (let [[~'etype1 ~'cid1 ~'id1 ~'pl1] ~aritym ~'np (prn "foo_multi_2nd_let" ~'ctx ~'etype1 ~'cid1 ~'id1 ~'pl1)]
            (bar ~'ctx [~'etype1 ~'cid1 ~'id1 ~'pl1] ~@expr))
        "y"
          (let [[~'etype2 ~'cid2 ~'id2 ~'pl2] ~aritym]
            (bar ~'ctx [~'etype2 ~'cid2 ~'id2 ~'pl2] ~@expr))
        ))))))

(def foo (foo_multi { meta1 "m1" meta2 "m2" } [{start "s1" top "x"} 
  {start "s3" top "x"} {start "s2" top "y"}] [etype a1 a2 a3] (block {toc "c"} 
   (block {c1 "d"} (action1)) "end"))
   )

(let [myarr ["x" 100 200 {"p" 1 "q" 2}]] (apply foo myarr))

无法将条形从bar宏传递到block宏并获得java.lang.NullPointerException。 如果我注释了bar宏中的块调用,则会执行其余代码。

(defmacro bar [ctx arity & expr]
      `(let [~'t1 "lv" ~'np (prn "bar_1st_let" '~ctx ~ctx '~arity ~arity '~arity(resolve (first '~arity)) )
            ] 
            (comment block ~ctx ;;~ctx = {start "s1" top "x"}
              (fn '~arity ;; etype specific ~arity eg: [etype1 cid1 id1 pl1]
                (let [~'__execonceresult 1]
                  (do ~@expr) ;; uses etype1     
                )
              )
            )
        )
  )

下面的注释是调试行的输出:

"foo_multi_1st_let" [etype a1 a2 a3]
"foo_multi_b4_case" {start "s1", top "x"} "x" ["x" 100 200 {"p" 1, "q" 2}]
"foo_multi_2nd_let" {start "s1", top "x"} "x" 100 200 {"p" 1, "q" 2}
"bar_1st_let" ctx {start "s1", top "x"} [etype1 cid1 id1 pl1] ["x" 100 200 {"p" 1, "q" 2}] [etype1 cid1 id1 pl1] nil
"foo_multi_b4_case" {start "s3", top "x"} "x" ["x" 100 200 {"p" 1, "q" 2}]
"foo_multi_2nd_let" {start "s3", top "x"} "x" 100 200 {"p" 1, "q" 2}
"bar_1st_let" ctx {start "s3", top "x"} [etype1 cid1 id1 pl1] ["x" 100 200 {"p" 1, "q" 2}] [etype1 cid1 id1 pl1] nil

按照上面打印的调试行,在bar宏中,我无法解析第一个arity符号,并且将其打印为nil(不知道原因)。目的是将arity正确地从bar宏传递到block宏,并能够访问和打印action1宏中的start和etype1值。

2 个答案:

答案 0 :(得分:5)

我得到一个NullPointer,原因是您的action1宏返回了nil,而block宏试图执行来自action1的响应。拼接报价可以解决该问题。另外,在我看来,block中绑定值的引号太多,所以我也将它们删除了。

(defmacro block [bindings & body]
  (let [bs (->>
            (cond
              (map? bindings)    bindings
              (symbol? bindings) []
              :else              (throw (Exception. "bindings must be map or symbol")))
            (mapcat (fn [[k v]] [(if (symbol? k) k (symbol (name k))) v])))]
    `(let [~@bs]
     ~@body)))

第二,clojure.core/resolveonly look up vars in a namespace,而不是clojure.core/letclojure.core/fn创建的本地人。因此,如果您尝试解决本地问题,将会得到nil

(defmacro bar [bindings arity & expr]
  `(block ~bindings ;;~bindings = {start "s1" top "x"}
          (fn ~arity ;; '~arity = [etype1 cid1 id1 pl1]
            (let [~'__execonceresult 1]
              (do ~@expr)))))

(macroexpand-1 '(bar {start "s1" top "x"} [etype1 cid1 id1 pl1] (action1)))
;; =>
(do
 (user/block
  {start "s1", top "x"}
  (clojure.core/fn
   [etype1 cid1 id1 pl1]
   (clojure.core/let [__execonceresult 1] (do (action1))))))

因此foo_multi的这一部分现在可以运行。

(block {toc "c"} (block {c1 "d"} (action1)) "end")
;;=>
"action1" :start :etype1
"end"

foo_multi中:

(defn named-top? [m]
  (when (= (name m) "top")
    m)) 

(defmacro foo_multi [metadata ctxv aritym & expr]
  (prn "foo_multi" (map #(get % (some named-top? (keys %))) ctxv))
  `(let [~@(mapcat (fn [[k v]] [k v]) metadata)]
     (prn "foo_multi_1st_let" '~aritym)
     (fn ~aritym
       (for [~'ctx (filter #(= (get % (some named-top? (keys %))) ~'etype) '~ctxv)]
         (do #_ (prn "foo_multi_b4_case" ~'ctx ~'etype ~aritym)
             (case ~'etype
               "x"
               (let [[~'etype1 ~'cid1 ~'id1 ~'pl1] ~aritym ~'np (prn "foo_multi_2nd_let" ~'ctx ~'etype1 ~'cid1 ~'id1 ~'pl1)]
                 (bar ~'ctx [~'etype1 ~'cid1 ~'id1 ~'pl1] ~@expr))
               "y"
               (let [[~'etype2 ~'cid2 ~'id2 ~'pl2] ~aritym]
                 (bar ~'ctx [~'etype2 ~'cid2 ~'id2 ~'pl2] ~@expr))))))))

过滤器(filter #(= (get % (some named-top? (keys %))) ~'etype) '~ctxv)似乎会出错,因为除非强制将etype放在参数metadata中,否则~'idiom不存在。用gensym创建这些魔术局部对象通常是一个坏主意,因为您永远不知道自己要遮盖什么,并且只是在很远的地方进行怪异的动作。最好将syntax-quote local#的{​​{1}}功能用作described here

就像对调试策略的评论一样,尝试提取简化的最小用例可能会帮助您了解所发生的情况。我认为这段代码很混乱。有很多东西被黑在一起。听起来好像您是在关注Clojure和宏,并且一口气咬了太多东西。我认为您正在尝试使用这些宏复制词汇范围,但我不确定最终目标是什么。也许通读this会有所帮助。

此外,我怀疑您发现clojure.core/for懒惰时会遇到麻烦。

foo_multi返回一个函数,该函数返回一个函数列表。因此,要实际执行您编写的大部分代码,您需要调用这些函数。

(let [start  :start
      etype1 :etype1
      foo    (foo_multi {meta1 "m1" meta2 "m2" }
                        [{start "s1" top "x"} 
                         {start "s3" top "x"}
                         {start "s2" top "y"}]
                        [etype a1 a2 a3]
                        (block {toc "c"} 
                               (block {c1 "d"} (action1))
                               "end"))
      args   ["x" 100 200 {"p" 1 "q" 2}]
      fns    (apply foo args)]
  (map #(apply % args) fns))

如果您尝试使用地图而不是向量来复制词法范围,那么以下代码片段可能会帮助您重新思考方法:

(defmacro my-let [bindings & body]
  (let [bs (vec (mapcat (fn [[k v]] [k v]) bindings))]
    `(let ~bs
       ~@body)))

(defmacro my-multi-let [bindings-list & body]
  (->> bindings-list
       (map (fn [b] `(my-let ~b ~@body)))
       (cons `list)))

(macroexpand-1 '(my-let {a "a1" b "b1"} [a b]))
(macroexpand-1 '(my-multi-let [{a "a1" b "b1"} {a "a2" b "b2"}] [a b]))

答案 1 :(得分:1)

正如@ I0st3d所指出的,这可能是修改条形定义后的解决方案,而foo_multi将变得像上面的解决方案my-multi-let。

(defmacro action1 [] `(prn "action1" ~'start ~'etype))

(defn named-type? [m] (when (= (name m) "top") m))

(defmacro block [ctx & expr] 
  `(let [~@(mapcat (fn [[k v]] [k `~v]) ctx)] ~@expr))

(defmacro bar [bindings & body]
  `(block ~bindings (if (= ~'top ~'etype) (do 
     ~@body))))

(defmacro foo_multi [metadata bindings-list arity & body]
  (let [fns (->> bindings-list
       (map (fn [b] `(bar ~b ~arity ~@body))))
  ] `(block ~metadata (fn ~arity (do ~@fns)))
))

(def foo (foo_multi {meta1 "m1" meta2 "m2"} [{start "s1" top "x"} 
  {start "s2" top "y"}] [etype a1 a2 a3] 
(block {toc "c"} (block {c1 "d"} (action1)) "end")
))

(let [myarr ["x" 100 200 {"p" 1 "q" 2}]] (apply foo myarr))

在bar宏中,您将可以访问所有arity参数,因此您也可以根据需要创建针对etype特定符号而变化的map var。 您的(让[myarr [“ x” 100200 {“ p” 1“ q” 2}]](应用foo myarr))也将按预期工作。