Clojure,扩展为使用代码执行列表的宏

时间:2013-01-10 19:23:32

标签: macros clojure

好的所以我有这个宏应该采用不同数量的参数然后用try和catch执行它们。我假设如果参数列表arg-list大于2,则列表中的第一个元素是绑定,例如此[a 0]。因此arg-list可能如下所示:([s (FileReader. (File. "text.txt"))] (. s read))

这就是我想出来的:

(defmacro safe [& arg-list] (list 'if (list '< (list 'count arg-list) '2)
    (list 'try (list 'eval arg-list) (list 'catch 'Exception 'e 'e))
    (list 'do (list 'eval arg-list) (list 'try (list 'eval (list 'rest arg-list)) (list 'catch 'Exception 'e 'e)))))

我一直在努力让这个连续两天工作,但它永远不会奏效。当我尝试这个宏时,例如:

(safe (+ 2 3))

我收到此错误:

ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn  user/eval91 (NO_SOURCE_FILE:100)

我只与Clojure合作了四天,如果我的代码不好,请原谅我。

2 个答案:

答案 0 :(得分:4)

嗯......首先我建议你阅读clojure的宏语法。我会在这里提供一些原点,但我不打算深入探讨。

首先,这是你的宏。

(defmacro safe [bindings? & forms]                                              
  (let [bindings (if (and (even? (count bindings?)) (vector? bindings?))        
                   bindings? nil)                                               
        forms    (if bindings forms (cons bindings? forms))                     
        except  `(catch Exception e# e#)]                                       
    (if bindings                                                                
    `(let ~bindings (try ~@forms ~except))                                      
    `(try ~@forms ~except))))

现在可以继续。

Clojure(let)宏需要一个具有偶数个参数的向量,并支持一些名为destructuring的非常有趣的行为。出于这个宏的目的,我假设任何有效的绑定参数首先是一个向量,第二个是偶数长度。 (let)的评估将执行相同的检查,但是这个宏必须这样做,因为第一个表单可能不是绑定而是要评估的表单,并且在这种情况下应该表现出不同的行为。

对于宏本身,我使用(let)处理参数,符号bindings用于指示绑定的存在以及绑定向量(如果存在)的双重目的。 forms从参数中的初始绑定(clojure允许您这样做)重新定义为受bindings影响的值,该值是您希望在错误中执行的整个表单序列 - 包含环境。实际上并没有要求except符号,它只是为了避免在每个扩展案例中重复(catch)形式的代码重复。

我使用的符号`(称为反引用或反引号)在这里等同于普通引号('),除了clojure允许我在反引号形式而不是引用形式中使用宏扩展语法。宏语法包含〜(unquote)运算符和〜@(insert(unquote))uperator。使用这三个符号表示我已经定义了两个所需的情况,带有绑定表单的let,我插入绑定表单和要尝试的表单以及简单的try only case。

条件可以被删除以产生

(defmacro safe [bindings? & forms]                                              
  (let [bindings (if (and (even? (count bindings?)) (vector? bindings?))        
                   bindings? [])                                               
        forms    (if-not (empty? bindings) 
                   forms (cons bindings? forms))                     
        except  `(catch Exception e# e#)]                                      
    `(let ~bindings (try ~@forms ~except))))

但是当没有绑定表格时你会有一个多余的(let)。

答案 1 :(得分:1)

您不需要eval - 宏扩展的结果已经被评估。使用宏内部的语法引用最容易实现您想要的内容:

(defmacro safe [& args]
  (if (< (count args) 2)
    `(try ~@args (catch Exception e# e#))
    `(let ~(first args)
       (try ~@(rest args) (catch Exception e# e#)))))

(safe (+ 2 3)) => 5
(safe [x 3] (+ 2 x)) => 5
(safe (Integer/parseInt "a")) => #<NumberFormatException java.lang.NumberFormatException: For input string: "a">

您看到异常的原因是示例中的arg-list将是一个表单列表,在您的情况下,该列表包含一个列表'(+ 2 5)的项目。当您评估第一个项目是列表的列表时,首先评估内部表单,然后评估外部表单:

(eval '(+ 2 3)) => 5
(eval '((+ 2 3))) => (eval '(5)) => exception, because 5 is not a function.

您可以通过将(list 'eval arg-list)更改为(list 'eval (first arg-list))来修复您的宏。