谁能给我简单地解释一下 Clojure 传感器?

我已经尝试阅读这方面的资料,但我仍然不明白它们的价值,或者它们能代替什么。他们让我的代码更短,更容易理解还是什么?

更新

很多人发布了答案,但它会很高兴看到的例子,有和没有传感器的东西很简单,甚至像我这样的白痴可以理解。当然,除非传感器需要一定的高水平的理解,在这种情况下,我永远不会理解他们: (

21585 次浏览

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

这是一个体面的运行通过

与编写对旧的 mapfilterreduce等的调用相比,您获得了更好的性能,因为您不需要在每个步骤之间构建中间集合,并重复遍历这些集合。

reducers或将所有操作手动组合成一个表达式相比,您可以更容易地使用抽象、更好的模块化和处理函数的重用。

我发现阅读 传感器 -js中的例子可以帮助我具体地理解在日常代码中如何使用它们。

For instance, consider this example (taken from the README at the link above):

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]

For one, using xf looks much cleaner than the usual alternative with Underscore.

_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);

假设您想使用一系列函数来转换数据流。Unix shell 允许您对管道操作符进行这种操作,例如。

cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l

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

对于 Clojure 来说也是如此。有多种方法可以表示转换管道,但是取决于您如何进行转换,您最终可能会得到从一个函数传递到下一个函数的中间流。如果有大量数据,将这些函数组合成单个函数会更快。传感器使得这一点很容易做到。一个早期的 Clojure 创新,reducers,也允许您这样做,但是有一些限制。传感器消除了一些限制。

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

这个 是对传感器的一个很好的概述。

传感器是在不知道基本序列是什么(如何做)的情况下对一系列数据做什么的处方。它可以是任何序列、异步通道或者可观察到的。

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

好处是,每次添加新数据源时,您不必实现所有标准组合子。一次又一次。因此,作为用户,您可以在不同的数据源上重用这些配方。

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

  1. 嵌套调用

    (reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
    
  2. 功能组合物功能组合物

    (def xform
    (comp
    (partial filter odd?)
    (partial map #(+ 2 %))))
    (reduce + (xform (range 0 10)))
    
  3. 线程宏

    (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)

传感器是一种减少功能的组合方式。

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

传感器现在可以接受 + 函数,并使其成为一个两倍加函数(在添加之前将每个输入加倍)。这就是传感器看起来的样子(用最基本的术语来说) :

(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])

would now yield 12.

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

(reduce (double conj) [] [1 2 3])

会产生[246]

它们也与输入的类型无关。

多个传感器可以作为一个(可链接的)配方链接,以转换还原函数。

更新: 因为现在有一个关于它的官方网页,我强烈建议阅读它: http://clojure.org/transducers

传感器是(据我所知!)功能,采取一个 减少功能,并返回另一个。 还原函数是指

For example:

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

在这种情况下,我的传感器需要一个输入过滤函数,它应用于0,然后,如果该值是偶数?在第一种情况下,过滤器将该值传递给计数器,然后过滤下一个值。而不是先过滤,然后将所有这些值传递给 count。

在第二个例子中也是一样,它一次检查一个值,如果该值小于3,那么它就让 count 加1。

Rich Hickey 在2014年的陌生环路会议(45分钟)上做了一个关于“传感器”的演讲。

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

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

传感器的清晰定义如下:

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}])

我们想知道村子里有多少孩子?我们可以很容易地找到它与以下减速器:

;; 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

我希望你能找到有用的例子。你可以找到更多的 给你

希望能有帮助。

克莱门西奥 · 莫拉莱斯 · 卢卡斯。

我在博客中用 clojurescript 例子解释了序列函数如何通过替换还原函数来实现可扩展性。

这就是我读到的传感器的意义所在。如果你想想 cons或者 conj操作,它们在诸如 mapfilter等操作中是硬编码的,那么还原函数是无法实现的。

有了传感器,减少功能是解耦的,我可以替换它,就像我用本地 javascript 数组 push感谢传感器。

(transduce (filter #(not (.hasOwnProperty prevChildMapping %))) (.-push #js[]) #js [] nextKeys)

filter和朋友有一个新的1元操作,将返回一个转换功能,你可以用来提供自己的还原功能。

以下是我的(大部分)行话和代码自由的答案。

从两个方面考虑数据,流(随时间发生的值,例如事件)或结构(存在于某个时间点的数据,例如列表、向量、数组等)。

您可能希望对流或结构执行某些操作。映射就是这样一种操作。映射函数可以将每个数据项(假设它是一个数字)递增1,您可以想象这将如何应用于流或结构。

A mapping function is just one of a class of functions that are sometimes referred to as "reducing functions". Another common reducing function is filter which remove values that match a predicate (e.g. remove all values that are even).

传感器让你“包装”一个或多个还原函数的序列,并产生一个“包”(它本身就是一个函数) ,可以在流或结构上工作。例如,你可以“打包”一系列的减少函数(例如过滤偶数,然后映射结果数字以使它们增加1) ,然后在值的流或结构(或两者兼而有之)上使用传感器“打包”。

这有什么特别的?通常,无法有效地组合简化函数来同时处理流和结构。

所以对您的好处是,您可以利用这些函数周围的知识,并将它们应用到更多的用例中。你的成本是,你必须学习一些额外的机制(即传感器) ,给你这个额外的权力。

As far as I understand, they're like 积木, decoupled from the input and output implementation. You just define the operation.

As the implementation of the operation is not in the input's code and nothing is done with the output, transducers are extremely reusable. They remind me of 流动s in 阿卡溪流.

I'm also new to transducers, sorry for the possibly-unclear answer.