我已经尝试阅读相关内容,但我仍然不理解它们的价值或者它们所替代的内容。它们是否能够使我的代码更简洁易懂?
更新
很多人已经回答了,但是如果能举出具有简单性质的使用和不使用转换器的例子将会更好,这样即使像我这样的白痴也能够理解。除非转换器需要某种高层次的理解,否则我将永远无法理解它们 :(
我已经尝试阅读相关内容,但我仍然不理解它们的价值或者它们所替代的内容。它们是否能够使我的代码更简洁易懂?
很多人已经回答了,但是如果能举出具有简单性质的使用和不使用转换器的例子将会更好,这样即使像我这样的白痴也能够理解。除非转换器需要某种高层次的理解,否则我将永远无法理解它们 :(
变压器是一种针对数据序列的处理方案,不需要知道底层的数据结构(如何处理)。可以适用于任何序列、异步通道或者可观察对象。
它们具有可组合性和多态性。
好处在于,每次添加新的数据源时都不必再次实现所有标准合成器,反复操作。结果,您作为用户能够在不同的数据源上重用这些方案。
在 Clojure 版本 1.7 之前,您有三种编写数据流查询的方法:
嵌套调用
(reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
函数组合
(def xform
(comp
(partial filter odd?)
(partial map #(+ 2 %))))
(reduce + (xform (range 0 10)))
线程宏
(defn xform [xs]
(->> xs
(map #(+ 2 %))
(filter odd?)))
(reduce + (xform (range 0 10)))
使用转换器,您将像这样编写:
(def xform
(comp
(map #(+ 2 %))
(filter odd?)))
(transduce xform + (range 0 10))
它们都是一样的。区别在于你从未直接调用转换器,而是将它们传递给另一个函数。转换器知道该做什么,获取转换器的函数知道如何操作。组合器的顺序就像使用线程宏(自然顺序)编写它一样。现在,您可以将 xform
与通道重复使用:
(chan 1 xform)
变压器(Transducers)可以提高效率,让您以更模块化的方式编写高效的代码。
与组合调用旧的map
、filter
、reduce
等相比,使用变压器能够实现更好的性能,因为您无需在每个步骤之间构建中间集合,并反复遍历这些集合。
与reducers
或手动组合所有操作成为单个表达式相比,使用变压器可以获得更易于使用的抽象、更好的模块化以及处理功能的重复使用。
map
/reduce
使用中间集合,因为它们都构建了一个迭代器链。我错在哪里了? - Lyubomyr Shaydarivmap
和filter
在嵌套时会创建中间集合。 - noisesmith假设你想使用一系列函数来转换数据流。Unix shell 允许您使用管道运算符来执行此类操作,例如:
cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l
(上述命令会计算在用户名中字母r的用户数量,不区分大小写)。这是作为一组进程实现的,每个进程从前一个进程的输出中读取数据,因此有四个中间流。你可以想象一个不同的实现方式,将这五个命令组合成一个单一的聚合命令,它将从其输入读取数据并仅写入其输出一次。如果中间流很昂贵而组合很便宜,这可能是一个不错的折衷方案。
对于Clojure也是同样的道理。有多种方法来表达转换的流水线,但根据你的实现方式,你可能会得到从一个函数传递到下一个函数的中间流。如果您有大量数据,则将这些函数组合成单个函数会更快。Transducers使这变得容易。一个早期的Clojure创新,reducers也可以做到这一点,但有一些限制。Transducers消除了某些限制。
所以回答你的问题,transducers不一定会使你的代码更短或更易懂,但你的代码可能也不会变得更长或不易懂,如果你正在处理大量的数据,transducers可以使你的代码更快。
这是一个关于transducers的很好的概述。
pmap
,它似乎没有得到足够的关注。如果您正在对一个序列进行昂贵的函数映射,那么将操作并行化只需添加 "p" 即可。无需更改代码中的任何其他内容,而且现在就可以使用——不是 alpha 版本,也不是 beta 版本。(如果该函数创建中间序列,则转换器可能会更快,我猜测。) - Mars传输器是一种组合方式,用于缩减函数。
例如:
缩减函数是将两个参数作为输入的函数:到目前为止的结果和一个输入。它们返回一个新的结果(到目前为止)。例如+
:使用两个参数,您可以将第一个参数视为到目前为止的结果,将第二个参数视为输入。
现在,传输器可以接收+函数并将其变成两倍加函数(在添加之前将每个输入值加倍)。这是该传输器的基本形式:
(defn double
[rfn]
(fn [r i]
(rfn r (* 2 i))))
为了举例说明,将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
那么
(reduce (double +) 0 [1 2 3])
现在会产生12。
通过转换器返回的归约函数与结果如何累积无关,因为它们使用传递给它们的归约函数进行累积,而不知道累积方式。在这里,我们使用conj
而不是+
。 conj
接受一个集合和一个值,并返回一个新的集合,该值已附加到其中。
(reduce (double conj) [] [1 2 3])
会产生[2 4 6]。
它们也不受输入来源的影响。
多个转换器可以被链接成一个(可链接的)配方,以转换减少函数。
更新:由于现在有一个官方页面介绍它,我强烈建议阅读:http://clojure.org/transducers
double
和transduce
之间的关系是什么? - Mars才是关键。这就是使
(tr1 (tr2 (tr3 reducer)))成为可能的东西。在这里,
(reducer elemt accumtor) --> accumtor`(或任何其他参数组合)。那个页面用“whatever”代替“accumtor”简直糟糕透了。 :) - Will NessRich Hickey在2014年奇怪的循环会议(45分钟)上发表了一场有关“Transducers”的演讲。
他用真实世界的例子 - 机场处理行李来简单地解释了什么是Transducers。他清晰地区分了不同方面并将它们与当前方法进行对比。最后,他解释了它们存在的原因。
我发现阅读来自transducers-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]
首先,使用xf
比使用下划线的常规替代方案更加简洁。
_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);
t.into([], t.comp(t.map(inc), t.filter(isEven)), [0,1,2,3,4])
- Juan Castañeda例如:
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
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.
;; 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}])
如果我们想知道村庄里有多少孩子,可以使用以下 reducer 轻松找到答案:
;; 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
;; 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
;; 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
(transduce (filter #(not (.hasOwnProperty prevChildMapping %))) (.-push #js[]) #js [] nextKeys)
< p > filter
和其他相关函数现在有一个新的一元操作,它将返回一个转换函数,您可以使用它来提供自己的 reducing 函数。