clojure中的评估和宏

clojure中的评估和宏,clojure,macros,Clojure,Macros,我想做什么: (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

我想做什么:

(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#)))))
创建一个宏,该宏可以获取向量向量(异常处理逻辑,在下面的示例中称为处理程序),一些其他数据(易发生异常的主体,在下面的示例中称为主体),并生成弹弓尝试/捕获逻辑

e、 我想转身

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

我之所以想这样做,是因为我相信正常的try/catch语法总是冗长的,尤其是在捕获多个错误时

我能够非常接近,但我不知道如何正确地计算宏中的符号以得到我想要的。我相信下面的例子2是最有趣的

我迄今为止的尝试:

(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#)))))
1) 宏,该宏以列表的形式返回适当的数据,但我不想返回它,我想对它求值。在结果上调用
eval
而不是
pprint
,将得到

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

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

下面不起作用的宏调用给出错误:

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

3) 如果我引用处理程序和主体,则确实有效的函数,这是我真正想要避免的

(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")))

在写这个问题的过程中,我找到了答案

查看第一次尝试时出现的错误:调用java类时就像调用函数一样

经过一番玩弄之后,我发现引用异常类是可行的,但在宏中引用它们是行不通的。使用
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以获得实际计算的结果,我认为这在这种情况下不是一个坏的做法,但我不确定。

我注意到,您尝试需要执行一些代码来生成宏中使用的表单,并在引用中执行。正如@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__))
现在我们可以看到你的宏了<编写宏时,code>macroexpand-1和
macroexpand
非常方便。通过调用宏并提供使用宏的窗体,可以查看宏生成的内容。例如:

(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
生成代码,而是使用一个函数来处理异常(
handleexception
),并在一个通用的
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"

根据我对你目标的理解,我会这么做:

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

答复:

(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
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
好的,它可以按照我们的要求工作。现在我们可以将多方法定义包装到宏中,使其更易于管理:

(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+
添加宏。。让我们假设
尝试处理

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

(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
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
案例处理程序中)

虽然这是一个明显的解决方案,但我不认为这是op想要的:如果只需要一个处理程序向量,那么这个宏需要什么,并将其拼接为
catch
块?其目的似乎是允许将在别处定义的处理程序集合传递给宏。但它会引发一个错误:
(让[handlers[[Exception println][RuntimeException str]](cp handlers(throw(RuntimeException.error))))
。这就是他想用
eval
东西来管理的东西。是的,可能是这样的-我为这种情况添加了解决方案。很好,但op使用
slingshot
,它不仅可以按类型捕获异常,还可以按其他类型捕获异常,
匹配处理程序
[:测试抛出println]
至于我,我真的不认为用一个宏替换
try
try+
宏是一个好主意。它失去了可读性,并增加了一系列可能的错误。因为我不知道这样设计的背景,很难评论另一个设计是否更好,但我强烈同意这种方法引入了很多复杂性。如果这不仅仅是编写宏的练习,那么我可以问一下您想要实现什么?消除多个
catch
块重复?或者其他什么?是的,我的目标是可读性。我认为这是将潜在的多个catch块组合到某个变量中,我可以定义elsew这里。最好是包装弹弓。你不认为在异常可以引发的位置捕捉异常比将此逻辑移到其他位置更可读吗?为什么不在一个捕捉块中捕捉任何异常,然后将其传递给某个处理函数,该函数知道如何处理每种异常类型?可能是多方法或这太棒了!我已经躲开了