[Transducer] Step by Step to build a simple transducer

Transducers are composable algorithmic transformations. They are independent from the context of their input and output sources and specify only the essence of the transformation in terms of an individual element. Because transducers are decoupled from input or output sources, they can be used in many different processes - collections, streams, channels, observables, etc. Transducers compose directly, without awareness of input or creation of intermediate aggregates.

OK, short in description... let's see why we need it

  1. Normal Javascript `map`,`filter` create inter variable and loop though array everytime we call `map` or `filter` function, `transducer` can loop thought the array only ONCE and apply all the transofrmations necessary.
复制代码
const data = [1,2,3];
const inc = x => x + 1;
const double = x => 2 * x;
const lessThanThree = x => x < 3;
////////////////////
/**
 * Problem: We loop over array 3 times! We want to loop over only once
 * in order to improve the profermance.
 */
const res1 = data
    .filter(lessThanThree)
    .map(double)
    .map(inc)

console.log(res1)    // [3,5]
复制代码

 

  2. We don't want to introduce any mutation or impure function such as `forEach` does, transducer are mutation free.

复制代码
/**
 * Problem: it is not pure function and we do mutation. But it is faster
 * than we do .filter.map.map style, because it only loop the array once.
 */
let res2 = [];
data.forEach((x) => {
    let item;
    if (lessThanThree(x)) {
        item = inc(double(x))
        res2.push(item);
    }
})
console.log(res2)    // [3,5]
复制代码

 

  3. We want to style functional style to keep the code more readable, in the meanwhile improve the proferemance:

复制代码
/**
 * Good: We avoid the mutation and can be write as pure function and it only loop once!
 * Problem: But we lose our function composion style! We still want .filter.map.map styling.
 * Meanwhile it should be profermance wise.
 */
const res3 = data.reduce((acc, curr) => {
    if (lessThanThree(curr)) {
        acc.push(inc(double(curr)));
    }
    return acc;
}, []);
console.log(res3);    // [3,5]
复制代码

 

OK, until now, we have some idea, what kind of code we want. Basiclly it should be composable and efficient.

The question is how to make composable code?

As we might know about, in OOP; if we want to chain multi function calls, from each function, we need to return `this`:

复制代码
Class Bot {
    ...
    sayName() {
        console.log(this,name)
        return this;
     }

    sayHello() {
        console.log("Hello")
        return this;
    }

}

const b = new Bot('Petter')

b.sayName().sayHello() 
复制代码

For Array, the reason we can chain calls together is because each call return Array type. The same as String, number...

 

The key is we need to keep the input and output as the same type!

Therefore for function, we need to keep input function and output function have the same function signature! 

复制代码
//data.reduce(reducer, seed), reducer is something we can compose!
//Because reducer :: (acc, curr) => acc
//For every reducer functions' signature are the same.
//If the function sinature are the same, then we can compose function together!
const _mapReducer = (xf, array) => 
    array.reduce((acc, curr) => {
        acc.push(xf(curr))
        return acc;
    }, []);
const _filterReducer = (xf, array) => 
    array.reduce((acc, curr) => {
        if (xf(curr)) acc.push(curr);
        return acc;
    }, []);
// To make fns easy to compose, we extract 'array' data & init value
const mapReducer = (xf) => ((acc, curr) => {
    acc.push(xf(curr))
    return acc;
});
const filterReducer = pred => ((acc, curr) => {
    if (pred(curr)) acc.push(curr);
    return acc;
});
// now mapReducer and filterReducer both have the same function signature.
console.log(data.reduce(mapReducer(double), [])); // [2,4,6]
console.log(data.reduce(mapReducer(inc), [])); // [2,3,4]
console.log(data.reduce(filterReducer(lessThanThree), []));  // [1,2]
复制代码

 

In order to compose reudcers together we need to make mapReducer and filterReducer as high order functions to take reducer as arguement, take a reducer as input and return a reducer signature as output is the key to do composion!
复制代码
// In order to compose reudcers together we need to make mapReducer and filterReducer as high order functions to take reducer as arguement
// Take a reducer as input and return a reducer signature as output is the key to do composion!
const map = xf => reducer => ((acc, curr) => {
    acc = reducer(acc, xf(curr))
    return acc;
});
const filter = pred => reducer => ((acc, curr)=> {
    if (pred(curr)) acc = reducer(acc, curr) 
    return acc;
})
// For mapReducer and filterReducer, we both do acc.push()
// therefore we can extrat this as base reducer
const pushReducer = (acc, value) => {
    acc.push(value);
    return acc;
};
复制代码

 

Now we are able to use functional style and loop the array only once!
const doulbeLessThanThree = compose(
    map(inc),
    map(double),
    filter(lessThanThree)
)
const res5 = data.reduce(doulbeLessThanThree(pushReducer),  []);
console.log(res5); // [3,5]

 

Define our transducer!

复制代码
/**
 * transducer :: ((a -> b -> a), (a -> b -> a), [a], [a]) -> [a]
 * @param {*} xf: base reducer 
 * @param {*} reducer: the composion redcuer signature
 * @param {*} seed : init value
 * @param {*} collection : data
 */
const transducer = (xf, reducer, seed, collection) => {
    return collection.reduce(reducer(xf), seed);
}
const res6 = transducer(pushReducer, doulbeLessThanThree, [], data);
console.log(res6); // [3,5]
复制代码

 

posted @   Zhentiw  阅读(276)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2018-02-20 [CSS3] Responsive Table -- no more table
2018-02-20 [Puppeteer] Get a Page's Load Time with Puppeteer (window.profermence.timing)
2018-02-20 [React] Use the new React Context API
2017-02-20 [Angular] @ViewChild and template #refs to get Element Ref
2017-02-20 [Angular] @ViewChildren and QueryLists (ngAfterViewInit)
2017-02-20 [Angular] Difference between Providers and ViewProviders
2016-02-20 [Protractor] Use protractor to catch errors in the console
点击右上角即可分享
微信分享提示