【问题标题】:How do I make this macro variadic in clojure?如何在 clojure 中制作这个宏变量?
【发布时间】:2013-02-04 12:28:02
【问题描述】:

我想做一个叫ds的东西,这样

(let [a 2]
  (ds a))

->

 "a->2"

(let [a 1 b 2 c 3]
    (ds a b c)) 

->

 "a->1, b->2, c->3"

到目前为止,我已经做到了:

(defmacro ds3 [a b c] 
     `(clojure.string/join ", " 
          [(str '~a "->" ~a) 
           (str '~b "->" ~b) 
           (str '~c "->" ~c)]))

这似乎有效:

 (let [ a 1 b 2 c 3]
     (ds3 a b c)) ; "1->1, 2->2, 3->3"

显然我可以定义 ds1 ds2 ds3 等...,但我想知道如何使其可变?

【问题讨论】:

    标签: macros clojure


    【解决方案1】:

    给你:

    (defmacro ds [& symbols]                                                                                                                             
      `(clojure.string/join ", "                                                                                                                         
                            ~(into [] 
                               (map (fn [s] `(str ~(name s) "->" ~s))  symbols))))                                                                
    

    【讨论】:

      【解决方案2】:

      Ankur 的回答可能是最实用的,但他将很多工作推迟到运行时,而这可以在宏扩展时完成。这是一个有用的练习,并且很好地演示了宏可以带来的强大功能,以了解您在编译时可以完成多少工作:

      (defmacro ds [& args]
        `(str ~(str (name (first args)) "->")
              ~(first args)
              ~@(for [arg (rest args)
                      clause [(str ", " (name arg) "->") arg]]
                  clause)))
      
      (macroexpand-1 '(ds a b c))
      => (clojure.core/str "a->" a ", b->" b ", c->" c)
      

      这避免了在运行时构建任何临时对象,并且绝对最小数量的字符串连接。

      【讨论】:

        【解决方案3】:

        编辑

        感谢@amalloy 的建议,这里有一些改进的宏不使用“严重错误”eval 并包括一些小测试:

        (import 'java.lang.ArithmeticException)
        
        (defmacro explain-expr
          "Produce a string representation of the unevaluated expression x, concatenated to
          an arrow and a string representation of the result of evaluating x, including
          Exceptions should they arise."
          [x]
          `(str ~(str x) " ~~> "
                (try ~x (catch Exception e# (str e#)))))
        
        (println (explain-expr (* 42 42)))
        (println (explain-expr (let [x 1] x)))
        (println (explain-expr (/ 6 0)))
        (println (let [x 1] (explain-expr x)))
        (let [y 37] (println (explain-expr (let [x 19] (* x y)))))
        (let [y 37] (println (explain-expr (let [y 19] (* y y)))))
        
        (* 42 42) ~~> 1764
        (let [x 1] x) ~~> 1
        (/ 6 0) ~~> java.lang.ArithmeticException: Divide by zero
        x ~~> 1
        (let [x 19] (* x y)) ~~> 703
        (let [y 19] (* y y)) ~~> 361
        
        (defmacro explain-exprs
          "Produce string representations of the unevaluated expressions xs, concatenated
          to arrows and string representations of the results of evaluating each
          expression, including Exceptions should they arise."
          [& xs]
          (into [] (map (fn [x]
                          `(str ~(str x) " ~~> "
                                (try ~x (catch Exception e# (str e#)))))
                        xs)))
        
        (clojure.pprint/pprint
         (let [y 37]
           (explain-exprs
            (* 42 42)
            (let [x 19] (* x y))
            (let [y 19] (* y y))
            (* y y)
            (/ 6 0))))
        
        ["(* 42 42) ~~> 1764"
         "(let [x 19] (* x y)) ~~> 703"
         "(let [y 19] (* y y)) ~~> 361"
         "(* y y) ~~> 1369"
         "(/ 6 0) ~~> java.lang.ArithmeticException: Divide by zero"]
        
        (defmacro explanation-map
          "Produce a hashmap from string representations of the unevaluated expressions
          exprs to the results of evaluating each expression in exprs, including
          Exceptions should they arise."
          [& exprs]
          (into {}
                (map (fn [expr]
                       `[~(str expr)
                         (try ~expr (catch Exception e# (str e#)))])
                     exprs)))
        
        (clojure.pprint/pprint
         (let [y 37]
           (explanation-map
            (* 42 42)
            (let [x 19] (* x y))
            (let [y 19] (* y y))
            (* y y)
            (/ 6 0))))
        
        {"(* 42 42)" 1764,
         "(let [x 19] (* x y))" 703,
         "(let [y 19] (* y y))" 361,
         "(* y y)" 1369,
         "(/ 6 0)" "java.lang.ArithmeticException: Divide by zero"}
        

        已弃用

        我把它留在这里是为了说明该做什么。

        这是一种适用于任何表达方式的变体(我认为)

        (defmacro dump-strings-and-values
          "Produces parallel vectors of printable dump strings and values. A dump string
          shows an expression, unevaluated, then a funny arrow, then the value of the
          expression."
          [& xs]
          `(apply map vector ;; transpose
                  (for [x# '~xs
                        v# [(try (eval x#) (catch Exception e# (str e#)))]]
                    [(str x# " ~~> " v#) v#])))
        
        (defmacro pdump
          "Print dump strings for one or more given expressions by side effect; return
          the value of the last actual argument."
          [& xs]
          `(let [[ss# vs#]
                 (dump-strings-and-values ~@xs)]
             (clojure.pprint/pprint ss#)
             (last vs#))
        

        一些示例:

        (pdump (* 6 7))
        

        打印["(* 6 7) ~~> 42"] 并返回42

        (pdump (* 7 6) (/ 1 0) (into {} [[:a 1]]))
        

        打印

        ["(* 7 6) ~~> 42"
         "(/ 1 0) ~~> java.lang.ArithmeticException: Divide by zero"
         "(into {} [[:a 1]]) ~~> {:a 1}"]
        

        并返回{:a 1}

        编辑

        我试图去掉打印输出中的外括号,即

        (defmacro vdump
          "Print dump strings for one or more given expressions by side effect; return
          the value of the last actual argument."
          [& xs]
          `(let [[ss# vs#]
                 (dump-strings-and-values ~@xs)]
             (map clojure.pprint/pprint ss#)
             (last vs#)))
        

        工作,我不知道为什么。它不打印输出,但宏扩展看起来不错。可能是 nREPL 或 REPL 问题,但我放弃了,只使用上面的那个,不用太担心括号。

        【讨论】:

        • 它不适用于闭包,因为您使用的是 eval。 eval 无权访问词法环境,因此这不起作用。令人高兴的是,按照其他答案中描述的方式,完全有可能在不使用 eval 的情况下完成您正在做的事情。我留给您使用与其他答案相同的技术来提供您所针对的更通用功能的练习。 (我对此投了反对票,因为使用 eval 是非常错误的;如果你在宏时间这样做,我很乐意转换为赞成票)。
        • 哦,仔细阅读您的答案,我发现我误解了您所说的无效。我的第一个意思是 (let [x 1] (vdump x)) 因为作用域而无法工作,但你可能没有尝试过。相反,您的代码无法打印,因为map is lazy.
        • @amalloy 哦,是的,我忘记了懒惰的地图。
        • @amalloy 我发布了一些更好的宏供您考虑。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-21
        • 1970-01-01
        • 2012-12-25
        相关资源
        最近更新 更多