【发布时间】:2015-07-13 17:26:48
【问题描述】:
使用 Clojure,假设 t 代表一个 Java 对象,而我有一个 [ts] 的集合。
如何将.setter 映射到[ts]?我在使用 (map #(.setter %1 %2) ts [vals]) 时遇到问题。之后当我访问关联的 getter 时,我会返回一个 nils 列表。
【问题讨论】:
-
你可能还喜欢
clojure.core/memfn
使用 Clojure,假设 t 代表一个 Java 对象,而我有一个 [ts] 的集合。
如何将.setter 映射到[ts]?我在使用 (map #(.setter %1 %2) ts [vals]) 时遇到问题。之后当我访问关联的 getter 时,我会返回一个 nils 列表。
【问题讨论】:
clojure.core/memfn
作为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.
了解有关惰性序列的更多信息是一件好事,因为它们是 Clojure 代码库中广泛使用的强大工具。他们带来的一个有点违反直觉的事情是创建无限序列的能力,即(范围)创建从 0 开始的所有数字的无限序列。所以这样做是完全合法的:
(map #(* % %) (range))
它将创建一个由所有数字组成的无限惰性平方序列!
【讨论】:
mapv 只是为了避免懒惰——它掩盖了程序员的意图。 doall 有一个目的,如果我在代码中看到 doall,我就知道它为什么在那里。
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! 不同,它不采用任意参数列表)
听起来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]))
【讨论】:
doall 更改为dorun。