Clojure:多个let绑定

7

在Java中,我通常这样做:

MyObject o1 = new MyObject();
o1.doSomething();
MyObject o2 = new MyObject();
o2.doWith(o1);
MyObject o3 = new MyObject();
o3.doWithBoth(o1, o2);

在Clojure中,如果我使用let绑定,它可能会像这样:
(let [o1 (create-obj)]
  (.doSomething o1)
  (let [o2 (create-obj)]
    (.doWith o2 o1)
    (let [o3 (create-obj)]
      (.doWithBoth o3 o1 o2))))

代码向右边增长很丑陋,也很难维护。有没有更好的方法解决这个问题?

3个回答

13
(let [o1 (doto (create-obj) (.doSomething))
      o2 (doto (create-obj) (.doWith o1))
      o3 (doto (create-obj) (.doWithBoth o1 o2))]
  ...)

详见(doc doto).

(更新:) 这是因为在每种情况下,都是在新创建的对象上调用方法。如果你想要在一个除第一个参数位置的其他位置传入新创建的对象来调用函数/方法,你可能最好使用 noisesmith 描述的 _ 技巧,虽然你也可以使用 dotoas->。后者的优点是不会引入未使用的局部变量(只有在 Clojure 检查到后续代码中实际引用了的局部变量才清除),但如果你仅仅是为了产生副作用而调用返回 void 的方法,那这当然无关紧要。


它将 (as-> expr sym form1 form2 ...) 转换为 (let [sym expr sym form1 sym form2 ...] sym)。请在 REPL(>= 1.5.0)中查看 (doc as->),或者访问 此链接 在 Clojure API 页面上查看相同内容。(我找不到稳定的 1.5.x API 链接,所以我链接到“当前”页面。) - Michał Marczyk
只是为了明确,你会在 doto 中使用 as->(doto 1 (as-> foo (+ foo 2) (* foo 3) (println foo))) 打印出 9 并返回 1。 - Michał Marczyk
我可以建议使用(new MyObject)代替(create-obj)吗?“惯用的Clojure代码直接调用Java库”Halloway。 - Thumbnail

3

常见做法是使用_作为let绑定来执行副作用的代码行。

(let [o1 (create-obj)
      _ (.doSomething o1)
      o2 (create-obj)
      _ (.doWith o2 o1)
      o3 (create-obj)]
  (.doWithBoth o3 o1 o2))

3
下面的解决方案虽然可行,但风格较差。 .dosomething等方法调用无疑会改变它们所应用的对象。因此,我们正在构造一个对象,将其绑定到本地名称,然后在幕后对其进行更改。噫!
Michal Marczyck的答案更好,因为doto返回经过改变的对象,然后将其绑定到本地名称,并且之后再也不进行修改。
我们不能指望Java互操作符合Clojure惯用法,但是我们应该尝试标记违规行为,就像这里的doto一样。
let绑定按从左到右的顺序进行。因此,您可以使用其中之一来执行上述操作:
(let [o1 (create-obj)
      _ (.doSomething o1)
      o2 (create-obj)
      _ (.doWith o2 o1)
      o3 (create-obj)]
        (.doWithBoth o3 o1 o2))

在这里我们将_绑定了两次。它是被忽略的绑定的传统名称。可以推测,.doSomething.doWith.doWithBoth都是为了副作用而执行的。


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