clojure中的评估和宏

时间:2016-04-28 01:56:17

标签: clojure macros

我正在尝试做什么:

创建一个可以采用向量矢量的宏(异常处理逻辑,在我的示例中在底部称为处理程序),一些其他数据(异常容易的主体,称为 body < / strong>在我的底部示例中,并生成弹弓try / catch逻辑。

e.g。我想转

(cp 
  ;; vector of vectors  (exception handling logic)
  [[Exception println]]
  ;;the "other data" exception prone body
  (throw+ (ex-info :crash "and burn")))

(try+
  (throw+ (ex-info :crash "and burn"))
  (catch Exception e (println e)))

我想这样做,因为我相信普通的try / catch语法总是很冗长,特别是在捕获多个错误时。

我能够非常接近但我无法弄清楚如何正确评估宏中的符号以获得我想要的东西。我相信下面的例子2是最有趣的。

到目前为止我的尝试:

1)宏返回适当的数据作为列表,但我不想返回它我想评估它。在结果上调用eval而不是pprint会产生

ClassCastException java.lang.Class cannot be cast to clojure.lang.IFn  stream-stocks.core/eval27882 (form-init2616933651136754630.clj:1)

(defmacro cp "handle exceptions"
  [handlers & body]
  `(loop [h# ~handlers
          acc# (conj '~body  'slingshot.slingshot/try+)]
     (if h#
       (recur (next h#)
              (concat acc# (list (list 'catch (first (first h#)) 'e# (reverse (conj (next (first h#)) 'e#))))  ))
       acc#)))

(let [handlers [[Exception println] [java.lang.NullPointerException type]
                [:test-throw #(println "Works! Will handle exception: " %)]]]
  (pprint (cp [[Exception println] [java.lang.NullPointerException type]
               [:test-throw #(println "Works! Will handle exception: " %)]]
              (slingshot.slingshot/throw+ {:test-throw "Test-throw error msg"})))
  (pprint (cp handlers
              (slingshot.slingshot/throw+ {:test-throw "Test-throw error msg"}))))

2)使用硬编码数据的宏,但不是符号

以下不起作用的宏调用会产生错误:

CompilerException java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol, compiling:(/tmp/form-init2616933651136754630.clj:6:3)

(defmacro cp "handle exceptions"
  [handlers2 & body]
  (loop [h# handlers2
         acc# (conj (list (first body))  'slingshot.slingshot/try+)]
    (if h#
      (recur (next h#)
             (concat acc# (list (list 'catch (first (first h#)) 'e# (reverse (conj (next (first h#)) 'e#))))))
      acc#)))


(let [handlers [ [Exception println] [java.lang.NullPointerException type]
                 [:test-throw #(println "Works! Will handle exception: " %)]]]
  ;;I work
  (cp [ [Exception println] [java.lang.NullPointerException type]
        [:test-throw #(println "Works! Will handle exception: " %)]]
      (slingshot.slingshot/throw+ {:test-throw "Test-throw error msg"}))
  ;;I do NOT work
  (cp handlers
      (slingshot.slingshot/throw+ {:test-throw "Test-throw error msg"})))

3)确实有效的功能 iff我引用处理程序和正文,我真的想避免

(defn cpf "handle exceptions" [handlers & body]
  (eval (loop [h handlers
               acc (conj body 'slingshot.slingshot/try+)]
          (if h
            (recur (next h)
                   (concat acc (list (list 'catch (first (first h)) 'e (reverse (conj (next (first h)) 'e))))))
            acc))))


(let [handlers [ '[Exception println] '[java.lang.NullPointerException type]
                 '[:test-throw #(println "Works! Will handle exception: " %)]
                 ]]
  (cpf [ '[Exception println]
         '[:test-throw println]

         ]
       '(println "Should get called")
       '(throw+ {:test-throw "Test-throw error msg"})
       '(println "Should not get called")
       )
  (cpf handlers
       '(println "Should get called")
       '(throw+ {:test-throw "Test-throw error msg"})
       '(println "Should not get called")))

3 个答案:

答案 0 :(得分:3)

我注意到你试图执行一些代码来生成要在宏中使用的表单,然后在引用中执行它。正如@leetwinski所评论的那样,可能是因为你的处理程序在编译时可能不知道。让我考虑两种情况。

在编译时知道处理程序向量

通过创建一些辅助函数然后在宏中使用它们来编写和测试会更容易。

我认为定义一个为给定的异常处理程序对生成catch形式的函数会很好:

(defn catch-handler [[exception handler]]
  `(catch ~exception e# (~handler e#)))

(catch-handler [Exception println])
;; => (catch java.lang.Exception e__20006__auto__
;; =>   (#function[clojure.core/println] e__20006__auto__))

现在我们可以进入你的宏。编写宏时,macroexpand-1macroexpand非常方便。您可以通过调用它们提供使用宏的表单来查看宏生成的内容。例如:

(macroexpand-1 '(when true (println "T")))
;; => (if true (do (println "T")))

让我们先生成所有的catch表单,然后在宏返回的引用表单中使用它们:

(defmacro cp [handlers & body]
  (let [catch-handlers (map catch-handler handlers)]
    `(try
       ~@body
       ~@catch-handlers)))

现在我们可以看到宏产生了什么:

(macroexpand-1
 '(cp [[Exception println] [RuntimeException str]]
      (throw (RuntimeException. "Error"))))

;; => (try
;; =>   (throw (RuntimeException. "Error"))
;; =>   (catch Exception e__20006__auto__ (println e__20006__auto__))
;; =>   (catch RuntimeException e__20006__auto__ (str e__20006__auto__)))

看起来宏生成了预期的代码。

在运行时

期间动态提供处理程序向量

在这种情况下,不是使用eval生成代码,而是使用函数来处理异常(handle-exception)并在通用catch Throwable块中使用它:

(defn matching-handler [handlers exception]
  (->> handlers
       (filter (fn [[exception-type handler]]
                 (instance? exception-type exception)))
       (first)
       (second)))

(defn handle-exception [handlers exception]
  (let [handler (or (matching-handler handlers exception)
                    #(throw %))]
    (handler exception)))

(defmacro cp' [handlers & body]
  `(try
     ~@body
     (catch Throwable e#
       (handle-exception ~handlers e#))))

(let [handlers [[RuntimeException println] [Exception str]]]
  (cp' handlers
       (throw (Exception.))))
;; => "java.lang.Exception"

答案 1 :(得分:1)

根据我对你的目标的理解,这就是我要做的事情:

首先,我将使用一个处理程序来处理所有异常,这将是多方法,因为它可以轻松确定如何处理不同类型的参数(包括inhetihance和自定义层次结构)。

(require '[slingshot.slingshot :as slingshot])

(defmulti my-exception-handler
  #(if (instance? Throwable %)
     (.getClass %)
     %))

(defmethod my-exception-handler NoSuchFieldError [error]
  (println "caught no such field error"))

(defmethod my-exception-handler :my-custom-error [error]
  (println "caught custom error"))

(defmethod my-exception-handler Error [error]
  (println "caught some error"))

(defmethod my-exception-handler :default [error]
  (println "caught something" error))

在repl中:

(slingshot/try+
  (slingshot/throw+ (Error. "asdd"))
  (catch Object o (my-exception-handler o)))

;; => caught some error

(slingshot/try+
  (slingshot/throw+ (NoSuchFieldError. "asdd"))
  (catch Object o (my-exception-handler o)))

;; => caught no such field error

(slingshot/try+
  (slingshot/throw+ :aaaa)
  (catch Object o (my-exception-handler o)))

;; => caught something :aaaa 

(slingshot/try+
  (slingshot/throw+ :my-custom-error)
  (catch Object o (my-exception-handler o)))

;; => caught custom error

好吧,它可以按照我们想要的方式工作。现在我们可以将multimethod定义包装到宏中,使其更易于管理:

(defmacro def-error-catcher [name definitions default-handler]
  `(do (defmulti ~name #(if (instance? Throwable %)
                          (.getClass %) %))
       ~@(for [[dispatch-val handler] definitions]
           `(defmethod ~name ~dispatch-val [v#]
              (~handler v#)))
       (defmethod ~name :default [v#] (~default-handler v#))))

所以你可以像这样使用它:

(def-error-catcher
 some-awesome-handler
 {NoSuchFieldError #(println :no-such-field (.getMessage %))
  NoSuchMethodError #(println :no-such-method (.getMessage %))
  Error #(println :error (.getMessage %))
  :my-custom-error println}
 #(println :unspecified %))

(您可以将处理程序作为地图传递,也可以作为矢量的矢量传递)

它扩展为:

(do
  (defmulti
    some-awesome-handler
    #(if (instance? java.lang.Throwable %) (.getClass %) %))
  (defmethod
    some-awesome-handler
    NoSuchFieldError
    [v__20379__auto__]
    (#(println :no-such-field (.getMessage %)) v__20379__auto__))
  (defmethod
    some-awesome-handler
    NoSuchMethodError
    [v__20379__auto__]
    (#(println :no-such-method (.getMessage %)) v__20379__auto__))
  (defmethod
    some-awesome-handler
    Error
    [v__20379__auto__]
    (#(println :error (.getMessage %)) v__20379__auto__))
  (defmethod
    some-awesome-handler
    :my-custom-error
    [v__20379__auto__]
    (println v__20379__auto__))
  (defmethod
    some-awesome-handler
    :default
    [v__20381__auto__]
    (#(println :unspecified %) v__20381__auto__)))

对于更多的糖,我们可以为try+添加宏..让我们说try-handle

(defmacro try-handle [handler & body]
  `(slingshot/try+
    ~@body
    (catch Object err# (~handler err#))))

在repl中:

user> (try-handle some-awesome-handler
        (slingshot/throw+ :my-custom-error))
:my-custom-error
nil

user> (try-handle some-awesome-handler
        (slingshot/throw+ (NoSuchFieldError. "no field")))
:no-such-field no field
nil

user> (try-handle some-awesome-handler
        (slingshot/throw+ (NoSuchMethodError. "no method")))
:no-such-method no method
nil

user> (try-handle some-awesome-handler
        (slingshot/throw+ (IllegalAccessError. "ill access")))
:error ill access
nil

user> (try-handle some-awesome-handler
        (slingshot/throw+ :something-else))
:unspecified :something-else
nil

注意它成功处理IllegalAccessError,因为我们的多方法知道继承,并执行正确的函数(在我们的情况下为Error处理程序)

答案 2 :(得分:-1)

在写这个问题的过程中,我找到了解决方案......

查看第一次尝试时的错误:正在调用java类,就像它是一个函数一样。

经过一些玩弄后,我发现引用Exception类会起作用,但在宏内引用它们不会。使用macroexpand来更好地了解发生了什么我发现我需要检查java类并将它们转回到try / catch期望的符号中。

固定代码:

(defmacro cp "handle exceptions"
  [handlers & body]
  `(eval (loop [h# ~handlers
                acc# (conj '~body  'slingshot.slingshot/try+)]
           (let [pred# (if (class? (first (first h#)))
                      (symbol (.getName (first (first h#))))
                      (first (first h#)))]
             (if (not (nil? h#))
               (recur (next h#)
                      (concat acc# (list (list 'catch pred# 'e# (reverse (conj (next (first h#)) 'e#))))  ))
               acc#)))))

我还在宏中添加了eval以获得实际评估的结果,我认为在这种情况下这不是一个坏习惯,但我不确定。