在第二个管道示例中,使用 `->>`, 你正在进行类似于 `into
` 版本的操作。通过获取与 `:tripData` 的关联来解包行程,然后获取与 `:segments` 的关联。不同之处在于,在 `into
` 版本中,你提供了一个类似外观的管道(原因在此未详细说明,由 `comp
` 来描述),这个管道实际上是一个花哨的循环,该循环遍历你正在归约的元素(`:segments
向量),并在应用隐式归约函数(此处可能是 `conj
` 或 `conj!
`,因为 `into
` 将使用临时的向量来更有效地构建)之前在循环内相应地转换元素。你可以将其视为在归约过程中,所有 `comp
` 中的函数按元素应用(从某种意义上说)。将其与第二个示例进行比较
第二个示例是基于惰性序列的,你将 `:segments
向量传送到一个通过映射和连接应用 `:dataPoints
并构建惰性序列的 `map
`。然后再次将其 `map
`(也产生另一个惰性序列,依赖于遍历 `mapcat
` 的输出),不断地进行。因此,实际上有三个相互依赖的惰性序列的堆栈。要遍历管道的输出,我必须遍历最后的惰性序列,然后这个惰性序列遍历其依赖的前序序列,然后那个前序序列遍历 `mapcat
`。这有点像过去的消防演习(现在人们使用水桶传递把水从水源运到火灾中,你必须把水桶传递给排在最后的一个人,让他倒水到火上)。序列化版本有一些开销,因为每个序列都必须为访问元素时分配一些中间对象、延迟求值函数和强制评估。这不是问题,但是存在开销,且随着你堆叠更多的序列,这种开销会增加。
我们不会在 `into
` 变体中出现这些开销,因为我们从不创建中间惰性序列。相反,我们只是应用了一组函数(而不是创建、强制和缓存多个依赖序列的元素)。消除这种开销可以产生显著节省。
注意:如果使用 `transduce
`,你不需要构建向量(正如 `into
` 所做的那样)。你还可以通过 `sequence
` 和 `eduction
` 来混合匹配序列和转换器。
(sequence (eduction (map inc) (range 10)))
转换器很棒,既因为效率很高,也因为它们很通用,并且很好地与序列和 core.async 通道配合。这里有很多实用功能。
然而,据我所知,你不能在 comp 中使用 map 关键字(例如,:tripData,:segments),因为关键字不是传感器。
你可以(如所演示)使用 `map` 关键字,就像
(map :blah)
这样可以产生一个转换器。
我不知道如何在效率和学会如何使用传感器之间取得平衡。
你可能看到,如果在 `transduce
` 的某个版本中构建字符串(比如 `clojure.string/join
`)比创建向量并发送到 `clojure.string/join
` 更快。
理想情况下,你会定义一个归约函数...
(let [res (->> trip
:tripData
:segments
(transduce xf (completing (fn [^java.lang.StringBuilder acc x]
(doto acc (.append sep) (.append (str x)))))
(java.lang.StringBuilder.)))]
;;get rid of the first comma
(doto res (.deleteCharAt 0) str))
这应该复制 `clojure.string/join
` 内部使用字符串构建器所做的,而不创建任何中间集合(如向量)。