2013-01-10 31 views
1

好吧,所以我有这个宏应该采取不同数量的参数,然后用try和catch执行它们。我假设如果参数列表arg-list大于2,那么列表中的第一个元素是一个绑定,例如[a 0]。所以arg-list可能看起来像这样:([s (FileReader. (File. "text.txt"))] (. s read))Clojure,宏扩展到执行代码列表

这是我想出来的:

(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四天所以原谅我,如果我的代码是坏的。

回答

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 [email protected] ~except))          
    `(try [email protected] ~except)))) 

而现在通过一个步行。

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

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

我使用的符号`(称为反引号或反引号)与普通引号(')相同,只是clojure允许我在反引用表单中使用宏扩展语法而不是引用形式。宏语法包含〜(unquote)运算符和〜@(insert(unquote))uperator。使用这三位表示法我已经定义了所需的两种情况,让我用一个绑定表单来插入绑定表单和要尝试的表单,以及简单的仅尝试的情况。

有条件可以消除产生

(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 [email protected] ~except)))) 

但后来你有多余的(让)时,有没有结合的形式。

+0

谢谢,非常详尽的答案!我会将它标记为解决方案,因为它是有效的,但是我将不得不再次研究这个答案,直到我理解了这一切。 – Topsic

1

你不需要eval - 宏观扩张的结果已经被评估过了。你想要的是最容易使用完成什么语法引用您的宏中:

(defmacro safe [& args] 
    (if (< (count args) 2) 
    `(try [email protected] (catch Exception e# e#)) 
    `(let ~(first args) 
     (try [email protected](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其第一个项目是一个列表,然后内部形式eval'ed先外形式eval'ed列表:

(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))固定。

+0

我将我的代码更改为[this](http://pastebin.com/usZQFrTF),但我仍然收到相同的错误 – Topsic

+0

我不确定你为什么需要处理具有多种形式的arg-list的情况 - “try”表单已经处理了多种形式。 – Alex

+0

@Ceilingbat你不应该在这个例子中用语法引用整个事物 - 你的语法 - 引用你想让宏发出的内容,但是你最初的if应该在语法引用之外,因为你决定了你的形式想要发射。如果你想传递绑定,你需要发出一个'let',而不是'do'。 – Alex