编写try块的Clojure宏

时间:2017-12-08 20:55:45

标签: clojure macros try-catch

问题

我正在尝试使用Clojure定义一个宏,它的工作方式类似于在java中尝试 它应该能够有一个绑定表单,例如[变量值],它可以绑定到一个可关闭的实例。

Java中的示例

try (Socket s = new Socket()) {
s.accept();
} catch(Exception e) {}

在此代码中,套接字'自动关闭,就好像有明确的finally子句

一样
finally {
if (s != null) s.close();
}

尝试解决方案

;I am defining my macro. It can either take one argument(expression) or can take two arguments(expression and a vector with two elements(a variable and value)
;expression is a form expression and should be able to be evaluated
(defmacro safe 
    ; if a vector and also an expression is passed into the macro
    [s NewS expression]
    ;I am defining my try block, and unquoting it with a ' so that the try block is not executed within the macro
    `(try   
        ;I am letting the variable(NewS) be equal to the value(s)
        (let [s NewS]
        ;I am trying the expression to see if it is valid and printing the exception if there is one
        (try 
            ~expression (catch Exception e (str "caught exception: " (.getMessage e))))
        )
        ;I am checking if my value is an instance of a java closeable
        (instance? java.util.Closeable s)
        ;I am catching the exception from the let statement if there is one
        (catch Exception e (str "caught exception: " (.getMessage e)))
    )
    ;if only an expression is passed into the macro
    [expression]
    `(try
        ~expression (catch Exception e (str "caught exception: " (.getMessage e)))
    )
)

输入和输出示例

user> (def v (safe (/ 1 0)))
user> v
#<ArithmeticException java.lang.ArithmeticException: Divide by zero>
user> (def v (safe (/ 10 2)))
user> v
5
user> (def v (safe [s (FileReader. (File. "file.txt"))] (.read s)))
user> v
105 ; first byte of file file.txt
user> (def v (safe [s (FileReader. (File. "missing-file"))] (. s read)))
user> v
#<FileNotFoundException java.io.FileNotFoundException:
missing-file (No such file or directory)>

错误消息

当我将这些示例输入放入我的main函数时,我得到了一个我不理解的编译器激活。

CompilerException clojure.lang.ArityException: Wrong number of args (1) passed to: core/safe, compiling:(/private/var/folders/6f/q7lhngtn45q_xpzd_24gjp2h0000gn/T/form-init2350735096437822603.clj:1:8) 

我不知道我可以在这个宏中调整什么,但我不能让它不返回错误。

更新

此解决方案几乎可以使用

(defmacro safe
    ([[s NewS] expression]
    `(try  
        (let [~s ~NewS] ~expression) (catch Exception e# (str "caught exception: " (.getMessage e#)))
    ))
    ([expression]
    `(try
        ~expression (catch Exception e# (str "caught exception: " (.getMessage e#)))
    ))
)

但以下测试失败

(defn -main     &#34;我做不了很多......但是。#34;     [&安培;参数]     (import java.io.FileReader java.io.File)     (def v(safe [s(FileReader。(File。&#34; file.txt&#34;))](。read s)))     (println v) )

user$ lein run
caught exception: file.txt (No such file or directory)
user$ cat file.txt 
teast

1 个答案:

答案 0 :(得分:1)

自动关闭行为已由with-open宏解决。请参阅实施here。这是(我认为)按照你的要求做出的一种表述。

(defmacro safe
  ([body]
   `(try ~body
      (catch Exception e#
        (str "caught exception: " e#))))
  ([bindings & body]
   `(try
      (with-open ~bindings
        ~@body)
      (catch Exception e#
        (str "caught exception: " e#)))))

使用示例:

(safe (/ 1 nil))
;;=> "caught exception: java.lang.NullPointerException"

(safe [s (clojure.java.io/reader "file.txt")]
  (prn (.read s)))
;;=> "caught exception: java.io.FileNotFoundException: file.txt (No such file or directory)"

(spit "file.txt" "contents here")
(safe [s (clojure.java.io/reader "file.txt")]
  (.read s))
;;=> 99

然而

  1. 在失败时返回字符串会让人感到困惑,因为如果期望的值也是字符串怎么办?那你怎么知道你的评价是成功还是失败?也许你只想打印/记录异常并返回nil? (在这种情况下,请考虑将str替换为println块中的catch。)
  2. 有两个safe的灵魂(一个只接受一个身体形态,另一个接受绑定为close d 身体形态)的问题令人担忧可以说是分开的。 with-open已经存在,当我们可以重复使用时,我们不应该重新发明它。
  3. 这个版本的safe可以使用任意数量的表单而不只是一个,所以它现在更加灵活。

    (defmacro safe
      [& body]
      `(try ~@body
         (catch Exception e#
           (println "caught exception:" (.getMessage e#)))))
    

    我们可以轻松使用 with-open中的safe 来获得您想要的行为:

    (safe
     (prn (/ 1 2)) ;; prints 1/2
     (with-open [s (clojure.java.io/reader "not_a_file.txt")]
       (.read s))) ;; fails, prints exception
    
    (safe
     (with-open [s (clojure.java.io/reader "file.txt")]
       (char (.read s)))) ;; returns the first char from file we `spit` above
    ;;=> \c