【问题标题】:Is my understanding and js code about transducer correct我对传感器的理解和js代码是否正确
【发布时间】:2019-10-23 17:03:55
【问题描述】:

我读过的关于传感器的网络文章

Js

  • 传感器:JavaScript 中的高效数据处理管道 @ Eric Elliott -Medium
  • 了解 JavaScript @ Roman Liutikov-Medium 中的转换器

半点都难以理解……

  • 什么是传感器?
  • 更简单的 JavaScript 转换器
  • 如何使用传感器更高效地进行数据转换

Clojure

  • Rich Hickey-cognitect 即将推出传感器
  • Transducers-Clojure.org

我阅读了大约 2 页的 Clojure 官方教程,并了解了基本语法。我参考了内置函数参考来理解传感器示例代码。

我对以上两篇文章的理解大概是75%...

我的问题

想知道下面的理解/js代码是对还是错。请帮帮我。

关于传感器

  1. compose() 返回的值是一个传感器。
  2. Transducer 通过作为参数传递给transduce() 函数来执行,此外,(2)Transducer 通过将数组直接传递给transducer() 来执行。
  3. 在(2)的过程中,没有生成中间值,而是执行如下链接的高效过程。

我的代码

"use strict";

const map = fn => arr => arr.map(fn),
filter = fn => arr => arr.filter(fn),
addReducer = arr => arr.reduce((acc, num) => acc + num, 0),
add1 = n => n + 1,
even = n => n % 2 === 0,

compose = (...fns) => initVal => fns.reduce((acc, fn) => fn(acc), initVal),
transduce = (xform, reducer, arr ) => reducer( xform(arr) );



const arr = [1,2,3],
transducer = compose(  /* called transducer or xform */
   map( add1 ), // 2,3,4
   filter( even ), // 2,4
);

console.log( transducer(arr) ) // 2,4
console.log( transduce(transducer, addReducer, arr) ) // 6

【问题讨论】:

  • compose 是错误的。当你通过compose(a, b)时,它会变成b(a(initVal)),而它应该以相反的顺序应用a(b(initVal))
  • 谢谢您的回答。我明白了。 compose() 是右结合的,而 pipe() 是左结合的,对吧?
  • 是的,你实现了pipe

标签: javascript clojure functional-programming


【解决方案1】:

转换器利用函数组合从 arity 中抽象出来的事实,即可以返回函数而不是“正常值”:

const comp = f => g => x => f(g(x));

const add = x => y => x + y;

const sqr = x => x * x;

const add9 = comp(add) (sqr) (3); // returns a lambda

console.log(
  add9(6)); // 15

现在换能器本身很无聊:

reduce => acc => x => /* body is specific to the transducer at hand */

它只是一个闭包,它需要一个 reducer(即一个组合了它的两个参数的二进制函数),然后可以直接输入你最喜欢的 reducer 函数。

让我们来看看地图转换器:

const mapper = f => (reduce => acc => x =>
  reduce(acc) (f(x)));

多余的括号只是说明了传感器的闭包。在这种情况下,它关闭了我们的转换函数f。接下来我们将应用它:

// map transducer

const mapper = f => reduce => acc => x =>
  reduce(acc) (f(x));

// my favorite fold (reducing function)

const arrFold = alg => zero => xs => {
  let acc = zero;

  for (let i = 0; i < xs.length; i++)
    acc = alg(acc) (xs[i], i);

  return acc;
};

// reducer

const add = x => y => x + y;

// transformer

const sqr = x => x * x;

// MAIN

const main = arrFold(mapper(sqr) (add)) (0);

console.log(
  main([1,2,3])); // 14

嗯,没那么令人印象深刻,对吧?换能器的真正威力源于其与功能组合的结合:

// map transducer

const mapper = f => reduce => acc => x =>
  reduce(acc) (f(x));

// filter transducer

const filterer = p => reduce => acc => x =>
  p(x) ? reduce(acc) (x) : acc;
  
// my favorite fold (reducing function)

const arrFold = alg => zero => xs => {
  let acc = zero;

  for (let i = 0; i < xs.length; i++)
    acc = alg(acc) (xs[i], i);

  return acc;
};

// helpers

const add = x => y => x + y; // reducer
const sqr = x => x * x; // transformer
const isOdd = x => (x & 1) === 1; // predicate
const comp = f => g => x => f(g(x));

// MAIN

const main = arrFold(comp(filterer(isOdd)) (mapper(sqr)) (add)) (0);

console.log(
  main([1,2,3])); // 10

虽然我们涉及两个转换器,但只有一次遍历 Array。这个属性称为循环融合。由于传感器组合返回另一个函数,因此评估顺序是相反的,即从左到右,而函数组合通常从右到左。

可重用性是另一个优势。您只需定义一次传感器,并且可以一次性使用所有可折叠数据类型。

同样值得注意的是,transduce 只是一个便利功能,对于理解这个概念并不重要。

关于传感器的内容就差不多了。

【讨论】:

  • 谢谢您的回答。我阅读并尝试理解您的答案。非常非常感谢。尤其是你回答的两句话让我意识到:(1)“转换器利用了函数组合从arity中抽象出来的事实,即可以返回一个函数而不是“正常值”。,(2)“同样值得注意的是transduce 只是一个方便的函数,对于理解这个概念并不重要。”
【解决方案2】:

您的代码与传感器无关。您对filterm̀ap 的定义表明它使用了普通的JS filtermap

const map = fn => arr => arr.map (fn),
const filter = fn => arr => arr.filter (fn),

const combo = compose(map(add1), filter(even));
combo(arr); ==> [2, 4]

发生的情况是,初始数组通过add1 传递给map,这将生成数组[2, 3, 4],然后通过even 传递给filter,并创建一个新数组[2, 4]

换能器也是如此:

const arr = [1, 2, 3];

const add1 = n => n + 1;
const even = n => n% 2 === 0;

const compose = (...fns) => {
  const [firstFunc, ...restFuncs] = fns.reverse();
  return (...args) => restFuncs.reduce((acc, fn) => fn(acc), firstFunc(...args));
};

const mapping = 
  fn => join => (acc, e) => join(acc, fn(e));

const filtering = 
  isIncluded => join => (acc, e) => isIncluded(e) ? join(acc, e) : acc;

const transducer = compose(mapping(add1), filtering(even));
const arrayJoin = (acc, e) => ([...acc, e]);
const result = arr.reduce(transducer(arrayJoin), []);
console.log(result);

所以不同之处在于,当您将 join 传递给传感器时,就会发生这种情况:

mapping(add1)(filtering(even)(arrayAdd))

filtering 是添加到某些集合的唯一步骤。当mapping 调用join 时直接调用filtering。这就是为什么签名(acc, e) 在工作部分和join 函数上是相同的。当代码运行时,添加和过滤同时完成,最终只有一个生成的数组,没有中间值。

【讨论】:

  • 感谢您的回答。哦,我得到了你的答案的前半部分!而现在,试着理解后半部分。 join 是必不可少的,关键部分...(现在继续了解...)非常非常重要!
猜你喜欢
  • 2020-03-03
  • 2012-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多