本文介绍了我如何优雅地结合资源和异常处理?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 29岁程序员,3月因学历无情被辞! 我正在为一个面向对象的API编写一个Clojure包装,它涉及资源处理。例如,对于Foo对象,我写了三个基本函数: foo?,它返回 true iff something是Foo; create-foo ,它尝试获取创建Foo的资源,然后返回一个包含返回码的映射,并且(如果构造成功)新创建的Foo;和 destroy-foo ,它需要一个Foo并释放其资源。下面是这三个函数的一些存根: (def foo? placeholder})) (defn create-foo [] (let [result(rand-nth [:: success :: bar-too-full :: baz-not-available] )] (merge {:: result result} (when(= :: success result) {:: foo:placeholder})))) (defn destroy-foo [foo] {:pre [(foo?foo)]} nil) 显然,每次 create-foo 被调用并成功,必须使用返回的Foo调用 destroy-foo 。下面是一个不使用任何自定义宏的简单示例: (let [{:keys [:: result :: foo]}(create-foo)] (if(= :: success result)(try (printlnGot a Foo:)(prn foo)(最后(destroy-foo foo)))(do (printlnGot a error: ))) 这里有很多样板: try - finally - destroy-foo 构造必须存在,才能确保释放所有Foo资源, (= :: success result)测试必须存在,以确保没有运行假设Foo时没有Foo。 某些模板可以通过 with-foo 宏消除,类似于 with-open 宏 clojure.core : (defmacro with-foo [bindings& body] {:pre [(vector?bindings)(= 2(count bindings))(symbol?(bindings 0))]} ` (try 〜@ body (finally (destroy-foo〜(bindings 0)))))) pre> 虽然这有帮助,但它不会对(= :: success result)样板,现在需要两个单独的绑定表单来实现所需的结果: (let [{:keys [:: result]:as m}(create-foo)] (if(= :: success result)(with-foo [ :foo m)] (printlnGot a Foo:)(prn foo))(do (printlnGot a error: (prn result)))) 我根本无法找出一个很好的方法来处理。我的意思是,我可以考虑 if-let 和 with-foo 转换为某种 if-with-foo 宏: (defmacro if-with-foo [bindings then else] {:pre [ ?bindings)(= 2(count bindings))]} `(let [{result#:: result foo#:: foo:as m#}〜(bindings 1) 〜(bindings 0)m#] (if(= :: success result#)(try 〜then (finally (destroy-foo foo# )))〜else))) p> (if-with-foo [{:keys [:: result :: foo]} -foo)] (do (printlnGot a Foo:)(prn foo))(do (printlnGot a result: )(prn result))) 但是,我不喜欢这个 if-with-foo 宏有几个原因: if-let 返回的映射结构 create-foo 其丑陋的名称反映了其丑陋的复杂性 这些宏是我能做的最好的吗?或者有一个更优雅的方式来处理资源处理与可能的资源获取失败?也许这是 monads 的工作; 解决方案建立从@ murphy的好主意,把错误处理程序放入 with-foo 的绑定保持焦点在正常情况下,我最终得到了一个我非常喜欢的解决方案: (b)(if-let [[sym初始化循环错误](非空绑定)] (let [error?(=:error temp)] `(let [{result#:: result foo#:: foo:as m# }〜init] (if(contains?m#:: foo)(try (let [〜sym foo#] (with-foo〜(subvec bindings如果错误?4 2))〜@ body))(finally (destroy-foo foo#)))(let [f#〜 (constant nil))] (f#result#)))))`(do 〜@ body))) if-with-foo 宏在问题中,这个 with-foo 宏仍然绑定到 create-foo 返回的结构;不同于我的 if-with-foo 宏和@ murphy的 with-foo 消除了用户手动拆分该结构的需要 所有名称都有适当的范围;用户的 sym 只绑定在主体正文中, $ c>:error 处理程序,相反, :: result 只绑定在:error 处理程序,不是在正文 一个漂亮,合适的名字,而不是像 if-with-foo 丑陋的东西,而不像@ murphy的 -foo 宏,此 with-foo 宏允许用户提供任何 init 值,而不是强制调用 create-foo ,并且不会转换返回的值 最基本的用例只是将一个符号绑定到一些正文中 create-foo >,如果构造失败,返回 nil : (with-foo [foo(create-foo)] [Got a Foo!foo]) 为了处理异常情况,可以将:error 处理程序添加到绑定中: (with-foo [foo(create-foo):error(partial vectorGot an error! b [Got a Foo!foo]) 可以使用任意数量的Foo绑定: / p> (with-foo [foo1(create-foo) foo2(create-foo )] [Got some Foos!foo1 foo2]) 它自己的:error handler;任何丢失的错误处理程序都替换为(constants nil): (with-foo [foo1(create-foo):错误(部分向量有一个错误!) foo2(create-foo)] [有一些Foos!foo1 foo2])I'm writing a Clojure wrapper for an object-oriented API that heavily involves resource handling. For instance, for the Foo object, I've written three basic functions: foo?, which returns true iff something is a Foo; create-foo, which attempts to obtain the resources to create a Foo, then returns a map containing a return code and (if the construction succeeded) the newly created Foo; and destroy-foo, which takes a Foo and releases its resources. Here are some stubs for those three functions:(def foo? (comp boolean #{:placeholder}))(defn create-foo [] (let [result (rand-nth [::success ::bar-too-full ::baz-not-available])] (merge {::result result} (when (= ::success result) {::foo :placeholder}))))(defn destroy-foo [foo] {:pre [(foo? foo)]} nil)Obviously, every time create-foo is called and succeeds, destroy-foo must be called with the returned Foo. Here's a simple example that doesn't use any custom macros:(let [{:keys [::result ::foo]} (create-foo)] (if (= ::success result) (try (println "Got a Foo:") (prn foo) (finally (destroy-foo foo))) (do (println "Got an error:") (prn result))))There's a lot of boilerplate here: the try-finally-destroy-foo construct must be present to ensure that all Foo resources are released, and the (= ::success result) test must be present to ensure that nothing gets run assuming a Foo when there is no Foo.Some of that boilerplate can be eliminated by a with-foo macro, similar to the with-open macro in clojure.core:(defmacro with-foo [bindings & body] {:pre [(vector? bindings) (= 2 (count bindings)) (symbol? (bindings 0))]} `(let ~bindings (try ~@body (finally (destroy-foo ~(bindings 0))))))While this does help somewhat, it doesn't do anything about the (= ::success result) boilerplate, and now two separate binding forms are required to achieve the desired result:(let [{:keys [::result] :as m} (create-foo)] (if (= ::success result) (with-foo [foo (::foo m)] (println "Got a Foo:") (prn foo)) (do (println "Got an error:") (prn result))))I simply can't figure out a good way to handle this. I mean, I could complect the behaviors of if-let and with-foo into some sort of if-with-foo macro:(defmacro if-with-foo [bindings then else] {:pre [(vector? bindings) (= 2 (count bindings))]} `(let [{result# ::result foo# ::foo :as m#} ~(bindings 1) ~(bindings 0) m#] (if (= ::success result#) (try ~then (finally (destroy-foo foo#))) ~else)))This does eliminate even more boilerplate:(if-with-foo [{:keys [::result ::foo]} (create-foo)] (do (println "Got a Foo:") (prn foo)) (do (println "Got a result:") (prn result)))However, I don't like this if-with-foo macro for several reasons:it's very tightly coupled to the specific structure of the map returned by create-foounlike if-let, it causes all bindings to be in scope in both branchesits ugly name reflects its ugly complexityAre these macros the best I can do here? Or is there a more elegant way to handle resource handling with possible resource obtainment failure? Perhaps this is a job for monads; I don't have enough experience with monads to know whether they would be useful tool here. 解决方案 Building from @murphy's excellent idea to put the error handler into with-foo's bindings to keep the focus on the normal case, I've ended up with a solution that I like quite a lot:(defmacro with-foo [bindings & body] {:pre [(vector? bindings) (even? (count bindings))]} (if-let [[sym init temp error] (not-empty bindings)] (let [error? (= :error temp)] `(let [{result# ::result foo# ::foo :as m#} ~init] (if (contains? m# ::foo) (try (let [~sym foo#] (with-foo ~(subvec bindings (if error? 4 2)) ~@body)) (finally (destroy-foo foo#))) (let [f# ~(if error? error `(constantly nil))] (f# result#))))) `(do ~@body)))like my if-with-foo macro in the question, this with-foo macro is still tied to the structure returned by create-foo; unlike my if-with-foo macro and @murphy's with-foo macro, it eliminates the need for the user to manually take apart that structureall names are properly scoped; the user's sym is only bound in the main body, not in the :error handler, and conversely, the ::result is only bound in the :error handler, not in the main bodylike @murphy's solution, this macro has a nice, fitting name, instead of something ugly like if-with-foounlike @murphy's with-foo macro, this with-foo macro allows the user to provide any init value, rather than forcing a call to create-foo, and doesn't transform the returned valueThe most basic use case simply binds a symbol to a Foo returned by create-foo in some body, returning nil if the construction fails:(with-foo [foo (create-foo)] ["Got a Foo!" foo])To handle the exceptional case, an :error handler can be added to the binding:(with-foo [foo (create-foo) :error (partial vector "Got an error!")] ["Got a Foo!" foo])Any number of Foo bindings can be used:(with-foo [foo1 (create-foo) foo2 (create-foo)] ["Got some Foos!" foo1 foo2])Each binding can have its own :error handler; any missing error handlers are replaced with (constantly nil):(with-foo [foo1 (create-foo) :error (partial vector "Got an error!") foo2 (create-foo)] ["Got some Foos!" foo1 foo2]) 这篇关于我如何优雅地结合资源和异常处理?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!
08-24 11:19