5

JavaScript玩转Clojure大法之Transducer

 3 years ago
source link: https://blog.oyanglul.us/javascript/clojure-essence-in-javascript-transducer
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
JavaScript玩转Clojure大法之Transducer

JavaScript玩转Clojure大法之Transducer

Table of Contents

📣 不要再找了, 弹幕功能已关闭

通过上一篇Clojure风格的JavaScript并发编程介绍了如何用JavaScript享受到Clojure在并发编程的优势. 我决定 写一系列关于如何用JavaScript玩转Clojure大法的文章. 这回要用JavaScript玩转另一个 Clojure全新的概念 – Transducer.

Transducer 是 Rich Hickey Clojure的作者 高调宣布 的在Clojure 1.7 版本加入的又一大法. 在之前的另一个概念 Reducer 却没那么 高调. 在解释transducer之前, 先看看什么是Reducer, 如果能看懂, 再接着看Transducer.

Reducer

说道reduce这个词, 想必JS Developer大多会用过underscore 我是故意吧reduce的参数顺序写"反"的, 原来underscore是先消费collection的. 至于为什么要反过来 可以参考这个解释 (或类似)的reduce方法, 大概形式是这样

_.reduce(fn, 0, [1,2,3])

大概意思是初始为0, 应用fn到每一个collection(检测coll)元素上,得到一个新的值.

如果加上map, 比如(我要开始用mori clojurescript作者把clojurescript的一些数据结构和函数编译成javascript, 这样就可以用普通js使用 clojure中的数据结构和函数. document严重过时, 建议看导入的源代码, 以及clojure的文档, 接口和clojure基本一致. 了)

reduce(sum, 0, map(inc [1,2,3]))

Terminology:

  1. reducing 函数: 用来reduce的函数, 比如sum
  2. transform: 变换, 从一个函数变另一个函数
  3. xf: xform, transform 函数
  4. reducible: 可被reduce的,也就是实现reduce接口的,比如所有的collection

让我们一步一步分析一下这次reduce到底干了什么

  1. map 函数 inc 到 coll 每一个元素, 得到一个新的 coll [2,3,4]
  2. reduce 把新coll的每个元素用sum函数, 得到一个新的值.

好吧这就是reduce了, 用一个reducing函数sum去计算coll得出一个新的值.

来看看更好的解法

transform

reduce函数需要等待map返回新的coll后才能reduce, 那么可不可以一步直接算出来呢?

假如我们有一个函数xf可以变换reducing函数(上例的sum是reducing函数)的形式, 比如

xf(reduceFn) -> anotherReduceFn

再假如我们的新map函数可以做这种转换

map(inc)(sum) -> aShinyNewReduceFn

map 函数的简单transform实现可以这样实现,如果你感兴趣的话

function map(fn){
  return function(reduceFn){
    return function(result, input){
      reduceFn(result, fn(input))
    }
  }
}

那么我们之前的reduce就可以写成

reduce(map(inc)(sum),0,[1,2,3])

yeah, 现在只需要一步就reduce出来结果了, reduce应用 map(inc)(sum) 来计算值, 只需要遍历一遍coll

Reducer

但是如果我们不想改变map函数的接口, 原始形式的接口还是比较好写好读的

reduce(sum, 0, map(inc [1,2,3]))

那么需要进一步的抽象, 我把新的map函数叫做rmap好了

function rmap(fn, coll){
  reducer(coll, map(fn))
}

跟以前接口一样,接收函数和coll,但是返回一个由reducer生成的reducible, 所以就变成了

reduce(sum, 0, reducer([1,2,3], map(inc)))

等等,怎么做到的…你已经消费了coll了, 那reducing函数怎么进来的, reducer怎么知道用sum去reduce呢.

Reducible

答案是, 反转reduce的关系, 原来reduce用sum去计算结果, 现在,我们调用reducible的reduce方法来计算结果

came-out.gif

如果你还没有被我弄晕的话, 准备好, 又来一个新单词 reducible. 也就是可以被reduce的东西.

于是我们需要coll实现reduce方法,这样就成为reducible了.

也就是reduce函数现在应该长这样, 我们暂且叫它 rreduce

function rreduce(reduceFn, init, reducible){
  reducible(reduceFn, init)
}

那么我们的例子就变成了这样

reducer([1,2,3], map(inc))(sum, 0)

reducer接收coll和xf, 返回reducible函数. 这一切都是lazy的, 直到rreduce调用第coll行才执行.

function reducer(coll, xf){
  return function(reduceFn, init){
    return coll.reduce(xf(reduceFn), init) (coll)
  }
}

Transducer

说了半天Reducer,明明说好的要解释的Transducer呢?

如果你还能follow, 那么现在要开始解释Transducer了

其实你已经见过Transducer了, 再回顾一下之前说的Reducer

  1. 接收一个xf函数和一个coll
  2. 用xf转换reducing函数, 并应用到coll

Transducer就是那个xf

reduce(map(inc)(sum),0,[1,2,3])

也就是这里面的 map(inc)

靠, 就这么简单?

就是这么简单, 前面说了reducer的出现是因为想保持原始reduce的api不便, 那么tranducer则提供了 另外一种reduce api

transduce(map(inc), sum, 0, [1,2,3])

transduce接收一个transducer,一个reducing function, 一个初始值, 一个coll. 这段代码跟前面干的事情一模一样.

另外牛逼的是transducer跟context完全没有关系, 就是完全与数据解耦开来, 比如我们组装好一个transducer xf

可以用在任何地方

seq(xf data) //生成一个lazy的序列, 同时lazy transform, 每次取的时候data会被transform
into([], xf data) //把 data transform后放到一个数组里
chan(1, xform) // 当数据经过CSP的channel时被transform

Is it Curry?

怎么看着有点像柯里化, 一样么?

当然不是, 柯里化或者部分参数只是部分配置参数, 而transducer是一次多n次转换的组合

比如一个柯里化的map可以

var mapinc = map(inc)
mapinc([1,2,3])
mapinc(sum)

因为map就俩参数, 第一个是函数第二个是data, 如果再给data会错误

但是tranceducer只是转换, 所以只接受reducing函数

reduce(mapinc(sum), 0, [1,2,3])
// => 9

Footnotes:

1

Clojure的作者

2

我是故意吧reduce的参数顺序写"反"的, 原来underscore是先消费collection的. 至于为什么要反过来 可以参考这个解释

3

clojurescript作者把clojurescript的一些数据结构和函数编译成javascript, 这样就可以用普通js使用 clojure中的数据结构和函数. document严重过时, 建议看导入的源代码, 以及clojure的文档, 接口和clojure基本一致.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK