了解clojure传感器的缺陷

mat*_*ter 5 clojure

clojure参考文献包含以下关于传感器的评论,这些评论似乎说明了写作和使用传感器的安全性:

如果您有应用传感器的新环境,则需要注意以下几条通用规则:

  • 如果步进函数返回减小的值,则转换过程不得再向步进函数提供任何输入.减少的值必须在完成前用deref打开.

  • 完成过程必须在最终累计值上调用完成操作一次.

  • 转换过程必须封装对通过调用转换器返回的函数的引用 - 这些对于跨线程使用可能是有状态的和不安全的.

你可以用一些例子来解释每个案例的含义吗?另外,在这种情况下,"背景"是指什么?

谢谢!

Pio*_*dyl 5

如果阶跃函数返回减小的值,则可转换过程不得向阶跃函数提供更多输入。减少的值必须在完成之前用 deref 解包。

这种情况的一个例子是take-while换能器:

(fn [rf]
  (fn
    ([] (rf))
    ([result] (rf result))
    ([result input]
      (if (pred input)
        (rf result input)
        (reduced result)))))
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,它可以返回一个reduced值,这意味着没有必要(实际上这将是一个错误)为这样的阶跃函数提供更多的输入——我们已经知道不能再产生更多的值了。

例如,在(1 1 3 5 6 8 7)使用odd?谓词处理输入集合时,一旦我们达到值6take-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))))))))
Run Code Online (Sandbox Code Playgroud)

如果您查看实现,您会注意到 step 函数不会返回它的累积值(存储在a数组列表中),直到谓词函数返回不同的结果(例如,在一系列奇数之后,它将收到偶数number,它将返回一系列累积的奇数)。问题是如果我们到达源数据的末尾 - 将没有机会观察谓词结果值的变化,并且不会返回累积值。因此,transducible 过程必须调用 step 函数(arity 1)的完成操作,以便它可以返回其累积结果(在我们的例子中(6 8))。

转换进程必须封装对通过调用转换器返回的函数的引用——这些引用可能是有状态的并且跨线程使用是不安全的。

当通过传递源数据和转换器实例来执行可转换过程时,它将首先调用转换器函数以生成步进函数。换能器是以下形状的函数

(fn [xf]
  (fn ([] ...)
      ([result] ...)
      ([result input] ...)))
Run Code Online (Sandbox Code Playgroud)

因此,transducible 过程将调用这个顶级函数(接受xf- 一个减少函数)以获得用于处理数据元素的实际步骤函数。问题是可转换过程必须保持对该阶跃函数的引用,并使用相同的实例来处理来自特定数据源的元素(例如,阶跃函数实例产生的partition-by转换器必须用于处理整个输入序列,因为它保持其内部如上所述)。使用不同的实例处理单个数据源会产生不正确的结果。

类似地,由于相同的原因,可转换过程不能重用步进函数实例来处理多个数据源 - 步进函数实例可能是有状态的,并保持用于处理特定数据源的内部状态。当阶跃函数用于处理另一个数据源时,该状态将被破坏。

也无法保证 step 函数的实现是否是线程安全的。

在这种情况下,“上下文”指的是什么?

“应用传感器的新环境”意味着实施一种新型的可转换过程。Clojure 提供了处理集合的可转导过程(例如intosequence)。core.async 库chan函数(它的一个参数)接受一个转换器实例作为参数,它通过将转换器应用于消耗的值来产生一个产生值(可以从通道消耗)的异步可转换过程。

例如,您可以创建一个可转换的进程来处理在套接字上接收到的数据,或者您自己的 observable 实现。

他们可以使用转换器来转换数据,因为当数据来自何处(套接字、流、集合、事件源等)时,转换器是不可知的——它只是一个用单个元素调用的函数。

他们也不关心(也不知道)应该对他们生成的结果做什么(例如,是否应该将其附加到结果序列(例如conj)?是否应该通过网络发送?插入到数据库中?) - 它是通过使用由 step 函数(rf上面的参数)捕获的减少函数来抽象的。

因此,我们不是创建一个仅使用conj或将元素保存到 db的 step 函数,而是传递一个具有该操作的特定实现的函数。您的可转换过程定义了该操作是什么。