如果阶跃函数返回一个减小的值,则可转换过程不得再向阶跃函数提供任何输入。减少的值必须在完成之前用 deref 解包。
这种情况的一个例子是take-while 传感器:
(fn [rf]
(fn
([] (rf))
([result] (rf result))
([result input]
(if (pred input)
(rf result input)
(reduced result)))))
如您所见,它可以返回一个reduced 值,这意味着没有意义(实际上这将是一个错误)为此类步进函数提供更多输入 - 我们知道已经不会产生更多值.
例如,在使用odd? 谓词处理(1 1 3 5 6 8 7) 输入集合时,一旦我们达到值6,take-while odd? 转换器创建的步进函数将不再返回值。
完成过程必须对最终累加值恰好调用一次完成操作。
这是一个传感器返回有状态步进函数的场景。 partition-by 传感器就是一个很好的例子。例如,当转导过程使用(partition-by odd?) 处理(1 3 2 4 5 2) 时,它将产生((1 3) (2 4) (5) (6 8))。
(fn [rf]
(let [a (java.util.ArrayList.)
pv (volatile! ::none)]
(fn
([] (rf))
([result]
(let [result (if (.isEmpty a)
result
(let [v (vec (.toArray a))]
;;clear first!
(.clear a)
(unreduced (rf result v))))]
(rf result)))
([result input]
(let [pval @pv
val (f input)]
(vreset! pv val)
(if (or (identical? pval ::none)
(= val pval))
(do
(.add a input)
result)
(let [v (vec (.toArray a))]
(.clear a)
(let [ret (rf result v)]
(when-not (reduced? ret)
(.add a input))
ret))))))))
如果你看一下实现,你会注意到 step 函数不会返回它的累积值(存储在 a 数组列表中),直到谓词函数返回不同的结果(例如,在一系列奇数之后numbers 它将收到一个偶数,它将返回一系列累积的奇数)。问题是如果我们到达源数据的末尾 - 将没有机会观察谓词结果值的变化,并且不会返回累积值。因此,可转换过程必须调用 step 函数(arity 1)的完成操作,以便返回其累积结果(在我们的例子中为 (6 8))。
转换进程必须封装对调用转换器返回的函数的引用 - 这些可能是有状态的,并且对于跨线程使用不安全。
当通过传递源数据和转换器实例来执行可转换过程时,它将首先调用转换器函数以产生步进函数。 The transducer is a function of the following shape:
(fn [xf]
(fn ([] ...)
([result] ...)
([result input] ...)))
因此,可转换过程将调用此顶级函数(接受xf - 一个归约函数)以获得用于处理数据元素的实际阶跃函数。问题是可转换过程必须保持对该阶跃函数的引用,并使用相同的实例来处理来自特定数据源的元素(例如,生成的阶跃函数实例partition-by 转换器必须用于处理整个输入序列,因为它如您在上面看到的那样保持其内部状态)。使用不同的实例来处理单个数据源会产生不正确的结果。
类似地,由于相同的原因,可转导流程无法重用步进函数实例来处理多个数据源 - 步进函数实例可能是有状态的,并为处理特定数据源保留内部状态。当 step 函数用于处理另一个数据源时,该状态将被破坏。
也不能保证步进函数的实现是否是线程安全的。
“上下文”在此上下文中指的是什么?
“应用换能器的新环境”是指实现一种新型的可转导过程。 Clojure 提供了与集合一起工作的可转导过程(例如into、sequence)。 core.async 库chan 函数(它的一个参数)接受一个transducible 实例作为参数,它通过将一个transducible 应用到消耗的值来产生一个异步的transducible 过程来产生值(可以从通道中消耗)。
例如,您可以创建一个可转换的过程来处理在套接字上接收到的数据,或者您自己的 observables 实现。
他们可以使用转换器来转换数据,因为转换器在数据来自何处(套接字、流、集合、事件源等)时是不可知的 - 它只是一个使用单个元素调用的函数。
他们也不关心(也不知道)应该如何处理他们生成的结果(例如,是否应该将其附加到结果序列(例如conj)?是否应该通过网络发送?插入到数据库中?) - 它是通过使用由 step 函数(上面的rf 参数)捕获的归约函数来抽象的。
因此,我们不是创建一个仅使用conj 或将元素保存到数据库的步进函数,而是传递一个具有该操作的特定实现的函数。您的可转导过程定义了该操作是什么。