有人可以用简单的术语向我解释Clojure Transducers吗?

Zub*_*air 89 clojure transducer

我已经尝试过阅读这个但我仍然不明白它们的价值或它们取代的东西.他们是否让我的代码更短,更容易理解?

更新

很多人发布了答案,但很高兴看到有和没有换能器的例子非常简单,甚至像我这样的白痴也能理解.除非传感器当然需要一定程度的理解,在这种情况下我永远不会理解它们:(

Ale*_*ček 68

传感器是如何处理数据序列的方法,而不知道底层序列是什么(如何做).它可以是任何seq,异步通道或可观察的.

它们是可组合的和多态的.

好处是,每次添加新数据源时都不必实现所有标准组合器.一次又一次.由此产生的效果,您作为用户能够在不同的数据源上重用这些配方.

广告更新

在Clojure的1.7版之前,您有三种方法来编写数据流查询:

  1. 嵌套调用
    (reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
Run Code Online (Sandbox Code Playgroud)
  1. 功能组成
    (def xform
      (comp
        (partial filter odd?)
        (partial map #(+ 2 %))))
    (reduce + (xform (range 0 10)))
Run Code Online (Sandbox Code Playgroud)
  1. 线程宏
    (defn xform [xs]
      (->> xs
           (map #(+ 2 %))
           (filter odd?)))
    (reduce + (xform (range 0 10)))
Run Code Online (Sandbox Code Playgroud)

使用传感器,您可以像下面这样编写:

(def xform
  (comp
    (map #(+ 2 %))
    (filter odd?)))
(transduce xform + (range 0 10))
Run Code Online (Sandbox Code Playgroud)

他们都这样做.不同之处在于你从不直接调用传感器,而是将它们传递给另一个函数.传感器知道该做什么,让传感器知道如何做的功能.组合器的顺序就像你用线程宏(自然顺序)编写它.现在您可以重复xform使用频道:

(chan 1 xform)
Run Code Online (Sandbox Code Playgroud)

  • 传感器似乎是抽象各种形式的可迭代对象的有用方式.这些可以是非消耗品,例如Clojure seqs,或消耗品(例如异步通道).在这方面,在我看来,如果您使用通道从基于seq的实现切换到core.async实现,那么使用传感器将极大地受益.传感器应该允许您保持逻辑的核心不变.使用传统的基于序列的处理,您必须将其转换为使用传感器或某些核心 - 异步模拟.这就是商业案例. (5认同)
  • 我更多的是在寻找一个带有示例的答案,该示例向我展示了换能器如何节省我的时间。 (4认同)
  • 这不是技术决定.我们只使用基于业务价值的决策."只要使用它们"就会让我解雇 (3认同)

noi*_*ith 41

传感器可提高效率,并允许您以更模块化的方式编写高效代码.

这是一个体面的贯穿.

相比构成调用老map,filter,reduce等你获得更好的性能,因为你并不需要每个步骤之间建立中间集合,并多次走的集合.

reducers手动将所有操作组合成单个表达式相比,您可以更轻松地使用抽象,更好的模块化和处理函数的重用.

  • @LyubomyrShaydariv通过"中间集合",noisesmith并不意味着"迭代/重新整合整个集合,然后迭代/重新统一另一个整个集合".他或她意味着当您嵌套返回序列的函数调用时,每个函数调用都会导致创建新的序列.实际的迭代仍然只发生一次,但由于嵌套的序列,还有额外的内存消耗和对象分配. (6认同)
  • 至少关于Clojure的懒惰版本,懒惰问题在这里是正交的.是的,map和filter是惰性的,当你链接它们时也会生成惰性值的容器.如果你没有抓住头部,则不会构建不需要的大型延迟序列,但仍然为每个惰性元素构建这些中间抽象. (4认同)
  • Clojure`map`和`filter`在嵌套时创建中间集合. (3认同)
  • 只是好奇,你上面说过:"在每一步之间建立中间集合".但是"中间收藏"听起来不像反模式吗?.NET提供了懒惰的枚举,Java提供了懒惰的流或Guava驱动的迭代,懒惰的Haskell也必须有懒惰的东西.这些都不需要`map` /`reduce`来使用中间集合,因为它们都构建了一个迭代器链.我错在哪里? (2认同)

use*_*464 20

假设您要使用一系列函数来转换数据流.Unix shell允许你用管道操作符来做这种事情,例如

cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l
Run Code Online (Sandbox Code Playgroud)

(以上命令计算用户名中带有大写或小写字母r的用户数).这是作为一组进程实现的,每个进程都从先前进程的输出中读取,因此有四个中间流.您可以想象一个不同的实现,它将五个命令组合成一个聚合命令,该命令将从其输入读取并将其输出恰好写入一次.如果中间流很昂贵,而且成分便宜,那可能是一个很好的权衡.

Clojure也有同样的东西.有多种方法可以表达转换管道,但是根据您的操作方式,最终可以得到从一个函数传递到下一个函数的中间流.如果您有大量数据,那么将这些函数组合成单个函数会更快.传感器可以很容易地做到这一点.早期的Clojure创新,减速器,让你也这样做,但有一些限制.传感器消除了一些限制.

因此,为了回答您的问题,传感器不一定会使您的代码更短或更易理解,但您的代码也可能不会更长或更不易理解,如果您处理大量数据,传感器可以使您的代码快点.

是传感器的一个很好的概述.

  • 值得一提的是`pmap`,它似乎没有得到足够的关注。如果你在一个序列上`map`ping一个昂贵的函数,那么使操作并行就像添加“p”一样简单。无需更改代码中的任何其他内容,它现在可用 - 不是 alpha,也不是 beta。(如果该函数创建中间序列,那么传感器可能会更快,我猜。) (2认同)

Leo*_*hin 20

传感器是减少功能的组合手段.

示例:减少函数是带有两个参数的函数:到目前为止的结果和输入.他们返回一个新的结果(到目前为止).例如+:使用两个参数,您可以将第一个视为目前的结果,将第二个视为输入.

换能器现在可以使用+功能并使其成为两倍以上的功能(在添加之前将每个输入加倍).这就是换能器的样子(在大多数基本术语中):

(defn double
  [rfn]
  (fn [r i] 
    (rfn r (* 2 i))))
Run Code Online (Sandbox Code Playgroud)

为了说明替代rfn,+看看如何+转换成两次加:

(def twice-plus ;; result of (double +)
  (fn [r i] 
    (+ r (* 2 i))))

(twice-plus 1 2)  ;-> 5
(= (twice-plus 1 2) ((double +) 1 2)) ;-> true
Run Code Online (Sandbox Code Playgroud)

所以

(reduce (double +) 0 [1 2 3]) 
Run Code Online (Sandbox Code Playgroud)

现在会产生12.

减少传感器返回的函数与结果的累积无关,因为它们会随着传递给它们的减少函数而累积,不知道如何.在这里我们使用conj而不是+.Conj获取一个集合和一个值,并返回一个附加了该值的新集合.

(reduce (double conj) [] [1 2 3]) 
Run Code Online (Sandbox Code Playgroud)

会产生[2 4 6]

它们也与输入的来源无关.

多个传感器可以链接为(可链接)配方,以转换减少功能.

更新:由于现在有关于它的官方页面,我强烈建议阅读它:http://clojure.org/transducers


小智 10

Rich Hickey在Strange Loop 2014大会(45分钟)上发表了"Transducers"演讲.

他用简单的方式解释了传感器是什么,用现实世界的例子 - 在机场处理袋子.他清楚地区分了不同的方面,并将它们与当前的方法进行了对比.最后,他给出了他们存在的理由.

视频:https://www.youtube.com/watch?v = 6mTbuzafcII


eno*_*com 8

我已经找到了来自传感器的例子- js帮我理解它们如何在日常代码中使用它们.

例如,考虑这个例子(取自上面链接的README):

var t = require("transducers-js");

var map    = t.map,
    filter = t.filter,
    comp   = t.comp,
    into   = t.into;

var inc    = function(n) { return n + 1; };
var isEven = function(n) { return n % 2 == 0; };
var xf     = comp(map(inc), filter(isEven));

console.log(into([], xf, [0,1,2,3,4])); // [2,4]
Run Code Online (Sandbox Code Playgroud)

例如,使用xf看起来比使用Underscore的通常替代品更清洁.

_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);
Run Code Online (Sandbox Code Playgroud)


Tob*_*ias 7

传感器是(据我所知!)功能,它们采用一种还原功能并返回另一种功能.减少功能是其中之一

例如:

user> (def my-transducer (comp count filter))
#'user/my-transducer
user> (my-transducer even? [0 1 2 3 4 5 6])
4
user> (my-transducer #(< 3 %) [0 1 2 3 4 5 6])
3
Run Code Online (Sandbox Code Playgroud)

在这种情况下,my-transducer采用输入过滤功能,如果该值为偶数,则应用于0.在第一种情况下,过滤器将该值传递给计数器,然后它过滤下一个值.而不是先过滤然后将所有这些值传递给count.

在第二个例子中它是一回事,它一次检查一个值,如果该值小于3,那么它允许计数加1.


小智 7

传感器清晰定义如下:

Transducers are a powerful and composable way to build algorithmic transformations that you can reuse in many contexts, and they’re coming to Clojure core and core.async.
Run Code Online (Sandbox Code Playgroud)

要理解它,让我们考虑以下简单示例:

;; The Families in the Village

(def village
  [{:home :north :family "smith" :name "sue" :age 37 :sex :f :role :parent}
   {:home :north :family "smith" :name "stan" :age 35 :sex :m :role :parent}
   {:home :north :family "smith" :name "simon" :age 7 :sex :m :role :child}
   {:home :north :family "smith" :name "sadie" :age 5 :sex :f :role :child}

   {:home :south :family "jones" :name "jill" :age 45 :sex :f :role :parent}
   {:home :south :family "jones" :name "jeff" :age 45 :sex :m :role :parent}
   {:home :south :family "jones" :name "jackie" :age 19 :sex :f :role :child}
   {:home :south :family "jones" :name "jason" :age 16 :sex :f :role :child}
   {:home :south :family "jones" :name "june" :age 14 :sex :f :role :child}

   {:home :west :family "brown" :name "billie" :age 55 :sex :f :role :parent}
   {:home :west :family "brown" :name "brian" :age 23 :sex :m :role :child}
   {:home :west :family "brown" :name "bettie" :age 29 :sex :f :role :child}

   {:home :east :family "williams" :name "walter" :age 23 :sex :m :role :parent}
   {:home :east :family "williams" :name "wanda" :age 3 :sex :f :role :child}])
Run Code Online (Sandbox Code Playgroud)

怎么样我们想知道村里有多少孩子?我们可以使用以下减速器轻松找到它:

;; Example 1a - using a reducer to add up all the mapped values

(def ex1a-map-children-to-value-1 (r/map #(if (= :child (:role %)) 1 0)))

(r/reduce + 0 (ex1a-map-children-to-value-1 village))
;;=>
8
Run Code Online (Sandbox Code Playgroud)

这是另一种方法:

;; Example 1b - using a transducer to add up all the mapped values

;; create the transducers using the new arity for map that
;; takes just the function, no collection

(def ex1b-map-children-to-value-1 (map #(if (= :child (:role %)) 1 0)))

;; now use transduce (c.f r/reduce) with the transducer to get the answer 
(transduce ex1b-map-children-to-value-1 + 0 village)
;;=>
8
Run Code Online (Sandbox Code Playgroud)

此外,在考虑子群时,它确实很强大.例如,如果我们想知道Brown Family中有多少孩子,我们可以执行:

;; Example 2a - using a reducer to count the children in the Brown family

;; create the reducer to select members of the Brown family
(def ex2a-select-brown-family (r/filter #(= "brown" (string/lower-case (:family %)))))

;; compose a composite function to select the Brown family and map children to 1
(def ex2a-count-brown-family-children (comp ex1a-map-children-to-value-1 ex2a-select-brown-family))

;; reduce to add up all the Brown children
(r/reduce + 0 (ex2a-count-brown-family-children village))
;;=>
2
Run Code Online (Sandbox Code Playgroud)

我希望你能找到有用的这些例子.你可以在这里找到更多

希望能帮助到你.

克莱门西奥莫拉莱斯卢卡斯.

  • 它更像是一个使命宣言,而不是一个定义. (6认同)
  • "Transducers是一种强大且可组合的方式来构建算法转换,您可以在许多环境中重用它们,它们将来到Clojure核心和core.async." 定义几乎适用于任何事情? (3认同)