【问题标题】:Mapping mutable methods onto Java objects in Clojure在 Clojure 中将可变方法映射到 Java 对象
【发布时间】:2015-07-13 17:26:48
【问题描述】:

使用 Clojure,假设 t 代表一个 Java 对象,而我有一个 [ts] 的集合。

如何将.setter 映射到[ts]?我在使用 (map #(.setter %1 %2) ts [vals]) 时遇到问题。之后当我访问关联的 getter 时,我会返回一个 nils 列表。

【问题讨论】:

  • 你可能还喜欢clojure.core/memfn

标签: java clojure interop


【解决方案1】:

作为ArrayList Java 类的示例。我创建了其中的三个,然后将“s1”添加到第一个,将“s2”添加到第二个,将“s3”添加到第三个。这是我的“二传手”。

然后我读取每个值的第一个值,我希望得到“s1”、“s2”、“s3”——所以在这个例子中这是一个 getter。

(let [ts [(java.util.ArrayList. 1)
          (java.util.ArrayList. 1)
          (java.util.ArrayList. 1)]]
  ; Add one element to each of the ArrayList's
  (doall (map #(.add %1 %2) ts ["s1" "s2" "s3"]))

  ; Verify that elements are indeed added.
  (doall (map #(.get %1 0) ts)))

此示例按预期工作 - 后者 mapv 返回 ("s1" "s2" "s3")

为什么类似的方法对您不起作用?好吧,我强烈怀疑那是因为你使用了map,它返回了一个惰性序列。

map 返回一个惰性序列,这意味着除非您尝试获取/使用它产生的值,否则它不会评估 - 而且您通常不需要来自 setter 的返回值。这意味着你的 setter 永远不会被调用。

这就是我使用 doall 的原因 - 它会采用惰性序列并实现它,这意味着每个元素都会被计算(在你的情况下 - 每个 setter 都会被调用)。

我的例子,当我在设置元素时只使用map 而不是doall,会失败:

(let [ts [(java.util.ArrayList. 1)
          (java.util.ArrayList. 1)
          (java.util.ArrayList. 1)]]
  ; Use a lazy map here - without doall
  (map #(.add %1 %2) ts ["s1" "s2" "s3"])
  (doall (map #(.get %1 0) ts)))

; java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

确实失败了,因为没有添加任何元素。

或者,您可以使用mapv 变体,它返回一个向量,因此始终不是惰性的:

(let [ts [(java.util.ArrayList. 1)
          (java.util.ArrayList. 1)
          (java.util.ArrayList. 1)]]
  ; Use a lazy map, but realize sequence using doall
  (doall (map #(.add %1 %2) ts ["s1" "s2" "s3"]))
  (mapv #(.get %1 0) ts))

正如@John Wiseman 在评论中指出的那样,mapv 可能是一个更糟糕的选择,因为它用于强制实现并不总是很清楚,而doall 则清楚明了。


关于惰性序列的一句话:

A lazy seq is a seq whose members aren't computed until you try to access them.

(source)

了解有关惰性序列的更多信息是一件好事,因为它们是 Clojure 代码库中广泛使用的强大工具。他们带来的一个有点违反直觉的事情是创建无限序列的能力,即(范围)创建从 0 开始的所有数字的无限序列。所以这样做是完全合法的:

(map #(* % %) (range))

它将创建一个由所有数字组成的无限惰性平方序列!

【讨论】:

  • 我不推荐使用mapv 只是为了避免懒惰——它掩盖了程序员的意图。 doall 有一个目的,如果我在代码中看到 doall,我就知道它为什么在那里。
  • @JohnWiseman - 好点子,我将重新排列答案,使其成为默认选择。
  • 在这种情况下,我会使用doseq 而不是doall。鉴于 OP 正在就地改变集合,因此无需生成另一个被丢弃的中间集合。
  • 一开始我以为doseq,但是有vals 的集合,我不知道如何最好地处理它们。 (doseq [[t v] (map vector ts vals)] (.setter t v)) 似乎毫无意义。
  • 如果您不访问操作的返回值,dorun 优于 doall。在 Clojure 1.7 中,您可以使用 run! 而不是 (dorun (map ...)) (这种情况除外,因为可悲的是,与 map run! 不同,它不采用任意参数列表)
【解决方案2】:

听起来setter 方法没有返回修改后的t 对象。如果你写了setter,你可以修改它,或者你只需​​要保留你原来的ts(但也要确保使用dorun来挤出map的懒惰) :

(let [ts ...]
  (dorun (map #(.setter %1 %2) ts [vals]))
  (println "Modified ts:" ts))

如果让map 返回集合对您来说更方便,另一种方法是执行以下操作:

(dorun (map #(do (.setter %1 %2) %1) ts [vals]))

【讨论】:

  • 根据@noisesmith 对另一个答案的评论,我将doall 更改为dorun
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-17
  • 2011-12-16
  • 2012-07-18
  • 2014-04-08
  • 1970-01-01
  • 2021-12-08
相关资源
最近更新 更多