Clojure 在宏上调用宏

Clojure 在宏上调用宏,clojure,macros,Clojure,Macros,我正在尝试编写一个Clojure宏,该宏创建一个前缀表示法列表,用于从一个简单的中缀表示法列表(例如(2*3+4*2)到一个已计算的(+(*23)(*42))(结果返回14) 我编写了以下代码: (defmacro infix [op inlist] (let [[i1 i2 & i3] inlist last-group? (nil? (second i3))] (if last-group? `(if (= ~op ~i2)

我正在尝试编写一个Clojure宏,该宏创建一个前缀表示法列表,用于从一个简单的中缀表示法列表(例如
(2*3+4*2)
到一个已计算的
(+(*23)(*42))
(结果返回14)

我编写了以下代码:

(defmacro infix [op inlist]
  (let [[i1 i2 & i3] inlist
        last-group? (nil? (second i3))]  

    (if last-group?
      `(if (= ~op ~i2)
        (~i2 ~i1 ~(first i3)) ; return unevaluated prefix list
        (~i1 ~i2 ~(first i3))) ; return unevaluated infix list
      `(if (= ~op ~i2)
         ; recur with prefix list in i1 position
         (infix ~op ~(conj (rest i3) (list i2 i1 (first i3)) ))
         ; return as list: i1 and i2, recur i3 (probably wrong)
         (~i1 ~i2 (infix ~op ~i3))
         ))))
通过使用不同的
op
(运算符函数)参数递归调用宏来强制运算符优先级:

(infix + (infix * (2 * 3 + 4 * 2)))
上面,我只是将它与两个
*
+
一起使用,但最终我希望为所有操作符调用宏(或者至少为了,/*+-)

执行上述嵌套宏调用时,出现以下错误:

CompilerException java.lang.RuntimeException: Can't take value of a macro: #'cbat.ch7.ex2/infix, compiling:(/tmp/form-init4661580047453041691.clj:1:1)
为单个运算符和同一运算符的列表调用宏(即
(中缀*(2*3*4))
)的效果与预期一样。如果我使用单个
(i1 i2 i3)
列表调用宏,如果
op
i2
不同,它会尝试(可以理解)返回未计算的中缀列表,并返回错误:

ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn  cbat.ch7.ex2/eval3003 (form-init4661580047453041691.clj:1)
我希望递归调用宏意味着我可以在计算整行之前处理未计算的中缀列表,但这似乎不起作用


我很确定后者的else分支,internal
if
(即
(~i1~i2(infix~op~i3))
)是不正确的,我可能只需要内部infix调用,但我更关心的是在求值之前获得不同操作符的嵌套宏调用

我知道这不是通常将中缀转换为前缀符号的方法,并且后来发现了,但请有人告诉我:

  • 这样的嵌套宏调用是否可行
  • 我的逻辑是否合理,是否离解决方案不远?如果是的话
  • 。。。我需要做哪些更改才能使事情正常运行

  • 我非常专注于学习Clojure,所以任何彻底的解释(如果可能的话)都是非常受欢迎的

    您可以嵌套宏调用,如下代码所示:

    (defmacro mac [tag & forms]
      `(do
         (println "mac - enter" ~tag)
         ~@forms
         (println "mac - exit " ~tag)))
    
    (mac :a
      (doseq [i (range 3)]
        (mac :b (println i))))
    
    mac - enter :a
    mac - enter :b
    0
    mac - exit  :b
    mac - enter :b
    1
    mac - exit  :b
    mac - enter :b
    2
    mac - exit  :b
    mac - exit  :a
    
    您还可以进行递归宏调用,如下所示:

    (defmacro macr [n]
      (if (zero? n)
        1
        `(* ~n (macr ~(dec n)))))
    
    (macr 5)   => 120
    
    在不深入研究您的具体实现的情况下,我建议两点:

  • 至少从一开始,让表单尽可能简单。这意味着只有像
    (2+3)
    这样的表单。特别是不要强迫宏在早期版本(或者永远)中计算运算符优先级

  • 宏几乎从来都不是必需的,不幸的是,在学习Clojure和其他Lisp时,宏有点“过度炒作”。我建议你在头一两年都不要考虑它们,因为它们比函数更脆弱,在重要方面也没有那么强大(例如,你不能将宏传递给函数)

  • 更新 每当你想写一些复杂的东西时(一个宏肯定是合格的!),从小处开始,然后开始 一步一个脚印地建立起来。使用并肯定有帮助 在这里

    首先,制作尽可能简单的宏并观察其行为:

    (ns tst.clj.core
      (:use clj.core clojure.test tupelo.test)
      (:require [tupelo.core :as t] ))
    (t/refer-tupelo)
    
    (defn infix-fn [[a op b]]
      (spyx a)
      (spyx op)
      (spyx b)
      )
    
    (defmacro infix [form]
      (infix-fn form))
    
    (infix (2 + 3))
    
    a => 2
    op => +
    b => 3
    
    对于许多宏,将marcro args发送到辅助函数(如
    infix fn
    )是很有帮助的。这个
    spyx
    通过打印符号及其值来帮助我们。此时,我们只需重新订购 将args转换为前缀符号,然后我们开始:

    (defn infix-fn [[a op b]] (list op a b))
    
    (defmacro infix [form] (infix-fn form))
    
    (deftest master
      (is= 5 (infix (2 + 3)))
      (is= 6 (infix (2 * 3))))
    
    如果我们有一个递归树结构呢?检查是否需要在中缀fn中递归:

    (declare infix-fn)
    
    (defn simplify [arg]
      (if (list? arg)
        (infix-fn arg)
        arg))
    
    (defn infix-fn [[a op b]]
      (list op (simplify a) (simplify b)))
    
    (is= 7 (infix ((2 * 2) + 3)))
    (is= 9 (infix ((1 + 2) * 3)))
    
    
    (is= 35 (infix ((2 + 3) * (3 + 4))))
    (is= 26 (infix ((2 * 3) + (4 * 5))))
    
    我不想增加运算符优先级的复杂性。如果绝对必要,我
    我不会自己编写代码,但会用于此目的。

    扩大通话范围会给你一个线索:

    (if (= + *) 
      (* infix (2 * 3 + 4 * 2)) 
      (infix * (2 * 3 + 4 * 2)))
    

    我猜,你有一个错误的假设,即宏的参数会在宏本身之前展开。但事实上在这一个:
    (~i2~i1~(first i3))
    i1
    仍然是
    infix
    符号。据我所知,解决办法是添加一些新的条件分支,以某种特殊方式处理中缀。

    Alan,谢谢你的回答,谢谢。我想说这是一个早期版本,但我已经尝试了好几个星期了。正如您所建议的,我试图简化一些事情,消除除三种符号形式以外的所有形式的功能,但仍然得到
    java.lang.RuntimeException:Can't take value of a macro
    error。请您提供一些特定于实现的指针好吗?Alan,再次感谢-因为我没有一个本地的Clojure法学家小组可以讨论,这一级别的细节正是我所需要的。一定要加入Clojure Google小组: