Paul Chiusano和Rúnar Óli撰写了一本精彩的书Functional programming in Scala。在其中他们提到了Scala社区中一个有点引用的概念 - Transducers。
在Clojure社区 - Transducers获得little more press。
我的问题是: Scala Transducers **(来自Scala中的功能编程一书)和Clojure Transducers之间有什么相同点和不同点?**
假设:
我知道
答案 0 :(得分:9)
书中Function Programming in Scala(FPiS)和Clojure传感器的流传感器非常相似。它们是使用“机器”(步骤函数)将输入流处理成输出流的概念的概括。 FPiS的传感器称为Process
es。 Rich Hickey also uses the term process在他关于Clojure传感器的介绍性演讲中。
FPiS传感器的设计基于Mealy machines。据说Mealy机器有:
transition function T : (S, I) -> S
output function G : (S, I) -> O
这些功能可以融合在一起形成:
step: (S, I) -> (S, O)
继续看,很容易看到步进功能对机器的当前状态和下一个输入项进行操作,以产生机器和输出项的下一个状态。
FPiS的一个组合器使用这样的步进功能:
trait Process[I, O] {
...
def loop[S, I, O](z: S)(f: (I,S) => (O,S)): Process[I, O]
...
}
这个loop
函数基本上是Rickey谈到的in this slide左边的种子缩减。
两者都可以在许多不同的环境中使用(例如列表,流,渠道等)。
在FPiS传感器中,过程类型为:
trait Process[I, O]
它所知道的是它的输入元素和它的输出元素。
在Clojure中,这是一个类似的故事。 Hickey称之为"fully decoupled"。
两种类型的传感器都可以组成。
FPiS使用“管道”操作符
map(labelHeavy) |> filter(_.nonFood)
Clojure使用comp
(comp
(filtering non-food?)
(mapping label-heavy))
在Clojure中:
reducer: (whatever, input) -> whatever
transducer: reducer -> reducer
在FPiS中:
// The main type is
trait Process[I, O]
// Many combinators have the type
Process[I, O] ⇒ Process[I, O]
然而,FPiS的表现不仅仅是一个功能。它是一个案例类(代数数据类型),有3种变体:Await,Emit和Halt。
case class Await[I,O](recv: Option[I] => Process[I,O])
case class Emit[I,O](head: O, tail: Process[I,O]
case class Halt[I,O]() extends Process[I,O]
reduced
的角色。两者都支持提前终止。 Clojure使用名为reduced
的特殊值来完成它,可以通过reduced?
谓词进行测试。
FPiS使用更静态类型的方法,进程可以处于以下3种状态之一:Await,Emit或Halt。当“步进函数”返回状态暂停的过程时,处理函数知道停止。
在某些方面,他们又是相似的。两种类型的传感器都是需求驱动的,不会产生中间集合。但是,我想象当流水线/组合时,FPiS的传感器效率不高,因为内部表示超过"just a stack of function calls" as Hickey puts it。我只是在这里猜测效率/性能。
查看fs2
(之前scalaz-stream
),了解一个基于FPiS中传感器设计的更高效的库。
以下是两个实现中filter
的示例:
Clojure,from Hickey's talk slides:
(defn filter
([pred]
(fn [rf]
(fn
([] (rf))
([result] (rf result))
([result input]
(if (prod input)
(rf result input)
result)))))
([pred coll]
(sequence (filter red) coll)))
在FPiS中,这是实现它的一种方法:
def filter[I](f: I ⇒ Boolean): Process[I, I] =
await(i ⇒ if (f(i)) emit(i, filter(f))
else filter(f))
正如您所看到的,filter
是由await
和emit
等其他组合器构建的。
在实施Clojure传感器时,有许多地方需要小心。这似乎是一种有利于提高效率的设计权衡。然而,这种下行似乎主要影响图书馆的生产者,而不是最终用户/消费者。
reduced
值,则必须再次使用输入调用该步骤函数。 FPiS的传感器设计有利于正确性和易用性。管道组合和flatMap
操作可确保及时完成完成操作并正确处理错误。这些问题不是传感器实现者的负担。也就是说,我想这个库可能没有Clojure那么高效。
Clojure和FPiS传感器都有:
他们的基本表现略有不同。 Clojure型换能器似乎有利于效率,而FPiS换能器有利于正确性和组合性。
答案 1 :(得分:0)
我不太熟悉Scala的传感器概念或术语无处不在,但是从上面发布的文本片段(以及我对传感器的了解),我可以这么说:
从上面的定义可以看出,任何函数或者可以使用大致沿着
行的签名来调用Stream[A] -> Stream[B]
因此,例如,在这种情况下,工作流的映射函数将被视为传感器。
就是这样;非常简单。
Clojure transducer是将一个缩小函数转换为另一个函数的函数。 reduce 函数是可以与reduce一起使用的函数。也就是说,如果Clojure有签名,它就会有签名
(x, a) -> x
在英语中,给定一些起始集合x
,并且正在减少集合中的“下一件事”a
,我们的reduce函数返回“正在构建的集合的下一次迭代”。
因此,如果这是减少函数的签名,则传感器具有签名
((x, a) -> x) -> ((x, b) -> x)
将传感器添加到Clojure的原因是,通过添加或core.async
通道,Rich Hickey和朋友发现自己重新调整所有标准集合函数以使用通道(map
,{{1 },filter
等)。 RH想知道这里是否有更好的方法,并开始思考如何从手头的集合类型的机制中decomplect这些各种集合处理函数的逻辑。准确地解释换能器是如何做到这一点的,我认为超出了这个问题的范围,所以我会回到这一点。但是如果你感兴趣的话,有很多关于这方面的文献很容易找到并且可以解决。
显然,这些是非常不同的概念,但这是我看到它们之间的关系:
虽然Scala传感器是Streams的集合处理函数(与其他Scala集合相似),但Clojure的传感器实际上是一种统一不同集合类型中集合处理函数实现的机制。因此,如果Scala有Clojure的传感器概念,Scala的传感器概念可以用Clojure的传感器概念来实现,这是一种更抽象/通用的处理函数可以重复用于多种集合类型。