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#)))))
