[英]Calling a macro on a macro
我正在尝试编写一个Clojure宏,它创建一个前缀表示法列表,用于评估从简单的中缀表示法列表,比如说(2 * 3 + 4 * 2)
到一个评估的(+ (* 2 3) (*4 2))
(导致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)
为单个运算符调用宏和相同运算符的列表(即(infix * (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分支,内部if
(即(~i1 ~i2 (infix ~op ~i3))
)是不正确的,我可能只需要内部中缀调用,但我更关心获取嵌套宏调用在评估之前工作的不同运算符。
我知道这不是将中缀转换为前缀表示法的常用方法,并且从那时起就发现了Dijkstra的分流码算法 ,但请有人亲切地告诉我:
我真的很专注于学习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点:
至少要开始,尽可能简化表单。 这意味着只有像(2 + 3)
这样的形式。 特别是不要强制宏在早期版本(或永远!)中找出运算符优先级。
Mac 几乎从来都不是必需的,不幸的是恕我直言,他们在学习Clojure和其他lisps时有点“过度炒作”。 我建议你甚至不要在第一年或第二年考虑它们,因为它们比功能更脆弱,而且在重要方面不那么强大(例如,你不能将宏传递给函数)。
每当你想写一些复杂的东西(一个宏肯定有资格!)时,从小处开始,一次一步地构建它。 使用lein-test-refresh插件和Tupelo库肯定有帮助。
首先,制作最简单的宏并观察其行为:
(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))))
如果我们有一个递归树结构怎么办? 检查我们是否需要在infix-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))))
我不想添加运算符优先级的复杂性。 如果绝对必要,我不会自己编写代码,但会为此目的使用优秀的Instaparse库 。
扩展你的通话会给你一个线索:
(if (= + *)
(* infix (2 * 3 + 4 * 2))
(infix * (2 * 3 + 4 * 2)))
你猜错了宏的论点会在宏本身之前扩展的假设。 但实际上在这一个: (~i2 ~i1 ~(first i3))
i1
仍然是中infix
符号。 据我所知,解决方案是添加一些新的条件分支,以一些特殊的方式处理中infix
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.