Javascript 如何在执行数组迭代时避免中间结果?

Javascript 如何在执行数组迭代时避免中间结果?,javascript,arrays,functional-programming,reduce,transducer,Javascript,Arrays,Functional Programming,Reduce,Transducer,使用数组时,需要定期使用中间表示法,尤其是在函数式编程中,在函数式编程中,数据通常被视为不可变的: const square = x => x * x; const odd = x => (x & 1) === 1; let xs = [1,2,3,4,5,6,7,8,9]; // unnecessary intermediate array: xs.map(square).filter(odd); // [1,4,9,16,25,36,49,64,81] => [1

使用数组时,需要定期使用中间表示法,尤其是在函数式编程中,在函数式编程中,数据通常被视为不可变的:

const square = x => x * x;
const odd = x => (x & 1) === 1;
let xs = [1,2,3,4,5,6,7,8,9];

// unnecessary intermediate array:
xs.map(square).filter(odd); // [1,4,9,16,25,36,49,64,81] => [1,9,25,49,81]

// even worse:
xs.map(square).filter(odd).slice(0, 2); // [1,9]

如何在Javascript/Ecmascript 2015中避免这种行为以获得更有效的迭代算法?

传感器是一种避免迭代算法中出现中间结果的可能方法。为了更好地理解它们,您必须意识到,传感器本身是毫无意义的:

// map transducer
let map = tf => rf => acc => x => rf(acc)(tf(x));
当所需函数始终相同时,我们为什么要为每次调用传递一个缩减函数到
map
,即
concat

该问题的答案见官方传感器定义:

传感器是可组合的算法变换

传感器仅在功能组合中发挥其表达能力:

const comp = f => g => x => f(g(x));
let xf = comp(filter(gt3))(map(inc));

foldL(xf(append))([])(xs);
comp
被传递一个任意数量的传感器(
filter
map
)和一个简化函数(
append
)作为其最终参数。从这个
comp
构建一个不需要中间数组的转换序列。每个数组元素在下一个元素对齐之前通过整个序列

此时,
map
传感器的定义是可以理解的:可组合性需要匹配的功能签名

请注意,传感器堆栈的评估顺序从左到右,因此与功能组合的正常顺序相反

传感器的一个重要特性是能够尽早退出迭代过程。在所选择的实现中,这种行为是通过以连续传递方式实现传感器和
foldL
来实现的。另一种选择是延迟评估。以下是CPS的实施:

const foldL = rf => acc => xs => {
  return xs.length
   ? rf(acc)(xs[0])(acc_ => foldL(rf)(acc_)(xs.slice(1)))
   : acc;
};

// transducers
const map = tf => rf => acc => x => cont => rf(acc)(tf(x))(cont);
const filter = pred => rf => acc => x => cont => pred(x) ? rf(acc)(x)(cont) : cont(acc);
const takeN = n => rf => acc => x => cont =>
 acc.length < n - 1 ? rf(acc)(x)(cont) : rf(acc)(x)(id);

// reducer
const append = xs => ys => xs.concat(ys);

// transformers
const inc = x => ++x;
const gt3 = x => x > 3;

const comp = f => g => x => f(g(x));
const liftC2 = f => x => y => cont => cont(f(x)(y));
const id = x => x;

let xs = [1,3,5,7,9,11];

let xf = comp(filter(gt3))(map(inc));
foldL(xf(liftC2(append)))([])(xs); // [6,8,10,12]

xf = comp(comp(filter(gt3))(map(inc)))(takeN(2));
foldL(xf(liftC2(append)))([])(xs); // [6,8]
constfoldl=rf=>acc=>xs=>{
返回xs.length
?rf(acc)(xs[0])(acc=>foldL(rf)(acc)(xs.切片(1)))
:acc;
};
//传感器
const map=tf=>rf=>acc=>x=>cont=>rf(acc)(tf(x))(cont);
const filter=pred=>rf=>acc=>x=>cont=>pred(x)?无线电频率(acc)(x)(续):续(acc);
const take=n=>rf=>acc=>x=>cont=>
附件长度ys=>xs.concat(ys);
//变形金刚
常数inc=x=>++x;
常数gt3=x=>x>3;
常数comp=f=>g=>x=>f(g(x));
常数liftC2=f=>x=>y=>cont=>cont(f(x)(y));
常数id=x=>x;
设xs=[1,3,5,7,9,11];
设xf=comp(filter(gt3))(map(inc));
foldL(xf(liftC2(append))([])(xs);//[6,8,10,12]
xf=组件(组件(过滤器(gt3))(映射(inc))(取(2));
foldL(xf(liftC2(append))([])(xs);//[6,8]
请注意,此实现更多的是概念验证,而不是全面的解决方案。传感器的明显优点是:

  • 没有中间表示
  • 纯功能简洁的解决方案
  • 泛型(使用任何聚合/集合,而不仅仅是数组)
  • 非凡的代码重用性(减速器/变换器是具有常见特征的常见功能)
理论上,CPS与命令式循环一样快,至少在Ecmascript 2015中是如此,因为所有尾部调用都具有相同的返回点,因此可以共享相同的堆栈帧(TCO)


对于Javascript解决方案来说,这种方法是否具有足够的惯用性被认为是有争议的。我更喜欢这种实用的款式。然而,最常见的转换器库是以对象样式实现的,OO开发人员应该更熟悉这些库。

您看过生成器吗?@deceze我对生成器几乎没有经验,但除了迭代器之外,它们似乎是一种可能的解决方案。你有代码示例吗?我没有MDN示例之类的,你可以自己用谷歌搜索。通常情况下,生成器允许您设置链,其中仅在请求时生成/返回下一项。一个生成器迭代一个生成器迭代一个生成器可以“惰性地”生成您需要的结果。