如何在Clojure中的try catch块中创建占位符变量

ste*_*t99 5 clojure

我有一些Java代码,我会这样写:

String var1;
String var2;
int var3;

try {
    String var1 = func1_that_might_throw();
    String var2 = func2_that_might_throw();
    int var3 = func3_that_might_throw();

    do_something_that_might_throw(var1, var2, var3);
} catch (Exception e) {
    cleanup_resources(var1, var2, var3);
}
Run Code Online (Sandbox Code Playgroud)

把它变成Clojure是一场噩梦.

(try+
  (let [var1 (func1_that_might_throw)
        var2 (func2_that_might_throw)
        var3 (func3_that_might_throw)]
    (do_something_that_might_throw var1 var2 var3))
  (catch Exception e
    (cleanup_resources var1 var2 var3)))
Run Code Online (Sandbox Code Playgroud)

问题是var1,var2,var3不要在catch块存在.移动let到外面try是有问题的,因为功能1到3可能会抛出并需要被捕获并清理资源.

我认为我可能需要的只是try之外的三个占位符变量,但是a)我甚至不知道clojure是否允许这样做,并且b)使一切变得更复杂.我不认为这是一个老将如何在clojure中做到这一点.这里有什么解决方案?

Art*_*ldt 3

这在很大程度上是程序设计和结构的问题。这听起来像是一个关于资源状态管理的先有鸡还是先有蛋的问题。如果您可以将该问题分解为:而不是解决这个问题,那么痛苦可能会少得多:

  • 创建状态并处理创建状态过程中的错误的函数
  • 另一个函数,它完成工作并处理工作时引起的错误。

这些是由一个函数解决的单独问题。如果你能把这些东西撬开,事情就会变得更容易。

现在,撇开说教不谈,Clojure 有很多处理可变状态的方法,并且不会假装全世界都可以被说服以“正确的方式”做事,并且 Clojure 确实允许您简单地创建和设置变量爪哇。您在实践中从未看到这些的原因是因为它们始终表明问题正在以倒退的方式解决或与另一个不同的问题混合在一起。

尽一切努力避免编写如下代码:

user> (def ^:dynamic var1)
#'user/var1
user> (def ^:dynamic var2)
#'user/var2
user> (def ^:dynamic var3)
#'user/var3

user> (defn do-stuff []
        (binding [var1 nil
                  var2 nil
                  var3 nil]
          (try
            (set! var1 42)
            (set! var2 43)
            (set! var3 44)
            (+ var1 var2 var3)
            (catch Exception e
              e))))
#'user/do-stuff
user> (do-stuff)
129
Run Code Online (Sandbox Code Playgroud)

我从来没有在任何实际问题中见过这样的代码(我和任何人一样将编写 Clojure 作为我的日常工作)并且这些事情基本上从来没有出现过。我写在这里是因为我不想让人们留下这样的印象:有些东西是你在 Clojure 中“不能写”的。

需要强调的是,这段代码具有 clojure 其余部分所具有的所有并发安全性。因为绑定创建遵循此代码的线程局部值,同时隔离 、 等的其他用户var1不受影响var2

一种更合理的方法是使用 futures(延迟创建这些)来定义要在 try-catch 之外运行的计算,而不实际运行它

然后代码进入 try catch 块并取消引用延迟的代码,从而使其实际运行。

user> (defn do-stuff []
        (let [var1 (delay (inc 41))
              var2 (delay (dec 43))
              var3 (delay (/ 42 0))]
          (try
            (+ @var1 @var2 @var3)
            (catch Exception e
              (println "cleaning up: " var1 var2 var3)))))
#'user/do-stuff
user> (do-stuff)
cleaning up:  #object[clojure.lang.Delay 0x3c55d284 {:status :ready, :val 42}]
              #object[clojure.lang.Delay 0x7bfa6dd1 {:status :ready, :val 42}]
              #object[clojure.lang.Delay     0x5e9e285b {:status :failed, :val #error {
              :cause Divide by zero
Run Code Online (Sandbox Code Playgroud)

这假设您可以更改清理代码以引用可能包含要清理的值或导致错误的异常的内容。delays 具有一个有用的属性,即可以在主函数中引发异常,然后在错误处理程序中再次引发。思考这个例子:

user> (def x (delay (/ 2 0)))
#'user/x
user> @x
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:158)
user> @x
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:158)
Run Code Online (Sandbox Code Playgroud)

您当然应该通过将函数分成两部分来解决这个问题,而不是通过使用set!动态变量。这是有可能的。