Clojure宏编写try块

3

问题

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

Java示例

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

在这段代码中,套接字's'会自动关闭,就好像有一个显式的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)>

错误信息

当我把这些示例输入放入我的主函数中时,我遇到了一个编译器异常,我不理解。

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 "我还没有做很多事情..." [& args] (import java.io.FileReader java.io.File) (def v (safe [s (FileReader. (File. "file.txt"))] (.read s))) (println v) )


(注:该段代码为Clojure语言)
user$ lein run
caught exception: file.txt (No such file or directory)
user$ cat file.txt 
teast

1
我认为你可以使用with-open来完成这个任务。 - Taylor Wood
我不是很明白我的主要问题在哪里。即使我使用 with open,我也会遇到类似的错误。(defmacro safe [& [s newS] expression] (if s `(let [~s ~newS])) `(with-open ~newS & ~expression) ) (defn -main [& args] (def v (safe (/ 1 0))) ) - Sam
你有检查过你的宏使用 macroexpand 后的结果吗?你在这里的代码格式很差,所以很难阅读。 - Carcigenicate
此外,Clojure 使用分号作为注释符号,而不是普通的井号。 - Carcigenicate
此外,考虑一下你的除以0示例中snewS的作用。 - Carcigenicate
@Carcigenicate 我发布了一个新的尝试解决方案的更新。我不知道在问题中还能修复什么,也不知道这些错误确切的含义。 - Sam
1个回答

2
自动关闭的行为已经由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?(在这种情况下,考虑在你的catch块中用println替换str。)
  2. 有两个safe的arity(一个只接受一个body形式,另一个接受要close的binding和body形式),这会纠缠应该分开的问题。当我们可以重用它时,已经存在with-open,我们不应该重新发明它。

这个版本的safe接受任意数量的forms而不只是一个,所以现在更加灵活。

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

我们可以轻松地在 `safe` 中使用 `with-open` 以获得您所需的行为:
(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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接