clojure引号和宏代码中的代字符
我是Clojure的新手,我无法理解其报价系统。 我写了一个宏,我做了两个类似的例子 - 一个正常,另一个不正常。 从某种意义上说,我只是试图围绕我的声明与try / catch条件。
以下是可用的代码:
(defmacro safe
[arg1 arg2]
(list 'let arg1 arg2)
)
这是不起作用的代码
(defmacro safe
[arg1 arg2]
'(try
~(list 'let arg1 arg2)
(catch Exception e (str "Error: " (.getMessage e)))
)
)
在~
符号之后,它应该避开引号,但由于某种原因,它似乎没有。 错误是:“在这种情况下无法解析符号:arg1 ...”。
谢谢你的帮助!
编辑:
我称之为宏的代码:
(println (safe [s (new FileReader (new File "text.txt"))] (.read s)))
另外,我输入这个:
(import java.io.FileReader java.io.File)
目标是从文件中读取第一个符号,同时避免不正确的文本文件名。 顺便说一句,这是我的学校作业,所以我不应该用其他方式来做到这一点,宏必须像这样被调用,我知道有关with-open
等。
转义( ~
)仅适用于准引号(也称为语法引用)。 您需要使用“back-quote”( `
,在与大多数美国键盘相同的键上找到~
),而不是普通的单引号( '
,与'
相同的键"
)。 ,这可能很容易错过。
您也可以通过不引用let
和un-quoting arg1
和arg2
来摆脱list
。 随着这些变化,我们得到这样的东西:
`(try ;; note back-quote, not regular quote.
(let ~arg1 ~arg2) ;; Getting rid of list — not necessary, but most find this more readable
(catch Exception e (str "Error: " (.getMessage e))))
现在,如果我们使用macroexpand
检查我们的进度:
(macroexand '(safe2 [s (new FileReader (new File "text.txt"))] (.read s)))
我们得到以下内容:
(try (clojure.core/let [s (new FileReader (new File text.txt))]
(.read s))
(catch java.lang.Exception user/e
(clojure.core/str Error: (.getMessage user/e))))
您可能会注意到,在Clojure中,编译宏时解析了准引号符号。 无法解析的符号使用当前命名空间(本例中为user
)进行限定。 这样做的基本原理是它可以帮助您编写“卫生”宏。 但是,在这种情况下,我们不想解析e
符号,因为不能给定局部变量的限定名称。
我们现在有几个选项。 首先是基本放弃卫生。 这在这种情况下起作用,因为你没有在catch
块中扩展任何用户提供的代码。 因此,名称e
可能与用户变量冲突。 这个解决方案看起来像这样:
`(try
(let ~arg1 ~arg2)
(catch Exception ~'e (str "Error: " (.getMessage ~'e))))
请注意使用~'e
而不仅仅是e
。 ~
是为了逃避准确的报价,然后我们用普通的报价来引用e
。 它看起来有点奇怪,但它有效。
虽然上面的解决方案有效,但使用生成的符号代替e
可能更好。 这样,如果您更改宏以接受用户为catch
块提供的代码,则可以确定它仍然可以工作。 在这种特殊情况下,“自动生成”符号完美符合法案。 这看起来如下:
`(try
(let ~arg1 ~arg2)
(catch Exception e# (str "Error: " (.getMessage e#))))
基本上,只要Clojure的读取器遇到一个符号以尾随#
准报价表内时,会产生一个新的gensym
'd符号和替换符号(即,每一次出现e#
与gensym
倒是一个。 如果我们macroexpand
这个,我们会得到如下的东西:
(try (clojure.core/let [s (new FileReader (new File text.txt))]
(.read s))
(catch java.lang.Exception e__66__auto__
(clojure.core/str Error: (.getMessage e__66__auto__))))
正如你所看到的, e#
每一次出现都被一台机器生成的符号所取代。 这里e__66__auto__
是自动生成的符号。
最后,虽然自动发电很方便,但并不总是足够的。 主要问题是,由于自动生成的符号是在读取时生成的,因此对每个准引号形式的评估(即宏的扩展)将使用相同的自动生成的符号。 在这个特别的情况下,没关系。 但是,如果使用嵌套宏表单,有时会导致冲突。 在这些情况下,每次扩展宏时都必须使用明确的gensym
符号。 用这种方法,你的宏的主体看起来像这样:
(let [e (gensym)]
`(try
(let ~arg1 ~arg2)
(catch Exception ~e (str "Error: " (.getMessage ~e)))))
这里e
是宏中的一个局部变量,它的值是一个新鲜的符号(通过gensym
)。 在准引用中,我们必须逃避e
才能使用gensym
的价值。
如果我们扩展这个,我们会得到如下的东西:
(try (clojure.core/let [s (new FileReader (new File text.txt))]
(.read s))
(catch java.lang.Exception G__771
(clojure.core/str Error: (.getMessage G__771))))
如果我们再次展开这个,我们会发现G__771
被另一个符号替换(也许G__774)。 相比之下,自动生成的解决方案( e#
)将始终对每个扩展使用相同的符号(至少在我们重新编译宏之前)。
希望这可以让你更好地理解宏,符号和卫生。 如果有什么不清楚,请告诉我。
这里有两个问题:
首先,unsplicing(〜和〜@)只能在syntax-quote(`)内部工作。 语法quote通常是为宏选择的,因为它也在宏定义位置进行符号名称空间解析。 简单的引号(')会使符号保持不变,所以ns的解析将在宏调用站点发生。 由于您无法控制您的宏将被调用的位置和方式,因此可能会非常混乱。
其次,你不能在引用的代码中声明新的符号,它可能会导致名称与宏周围的代码发生冲突。 宏引入的每个新符号都应该使用后缀#
这样Clojure宏展开将用新的自动生成的名称替换它,这个名称不会导致任何与用户代码的名称冲突。
(defmacro m []
`(let [x# 1]
x#))
(macroexpand-1 '(m)) =>
=> (clojure.core/let [x__6257__auto__ 1]
x__6257__auto__)
请注意let如何成为完全限定的clojure.core / let(以后避免ns解析的细微差别),并将x#替换为x__6257__auto__(避免名称冲突)。
你的代码应该写成这样:
(defmacro safe [arg1 arg2]
`(try
(let ~arg1 ~arg2)
(catch Exception e#
(str "Error: " (.getMessage e#)))))
像这样检查:
(macroexpand-1 '(safe [s (new FileReader (new File "text.txt"))] (.read s)))
↓↓↓
(try
(clojure.core/let [s (new FileReader (new File "text.txt"))]
(.read s))
(catch java.lang.Exception e__6283__auto__
(clojure.core/str "Error: " (.getMessage e__6283__auto__))))
我还会推荐使用宏指令的惯用名称,并提供任意长度的第二个参数:
(defmacro safe-let [bindings & body]
`(try
(let ~bindings
~@body)
(catch Exception e#
(str "Error: " (.getMessage e#)))))
链接地址: http://www.djcxy.com/p/65761.html