Tansducer-js lib
A high performance Transducers implementation for javascript.
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.
The whole point to use transducer-js lib is because in normal FP approach, when se do:
Array
.map
.filter
.reducer
We actually loop though the array 3 times. So time complexity is O(n). If you can reduce 3 times into single time. And reduce the time complexity from O(n) to O(1). It would be a huge proferemce improvement for large datasets.
Second, normal approach we are limiting ourself with Array type, we cannot call ‘mpa, filter, reduce‘ on type of Object, Map and Set. But with transducer-js, we are able to do that as well.
The primary distinguishing features of Ramda are:
Ramda emphasizes a purer functional style. Immutability and side-effect free functions are at the heart of its design philosophy. This can help you get the job done with simple, elegant code.
Ramda functions are automatically curried. This allows you to easily build up new functions from old ones simply by not supplying the final parameters.
The parameters to Ramda functions are arranged to make it convenient for currying. The data to be operated on is generally supplied last.
The last two points together make it very easy to build functions as sequences of simpler functions, each of which transforms the data and passes it along to the next. Ramda is designed to support this style of coding.
The whole point we are using Ramda.js is because it enforce the FP approach, and auto currying is a huge win in FP style. Also you can use loadsh/fp if you wish.
Now let‘s see what kind of problem we want to solve:
lets say we have two string:
`hostname, username `
` 10.1.10.1, zwan 10.1.11.1, wan `
And we want to apply so transform function to those string and the final output should be:
[ { hostname: ‘10.1.10.1‘, username: ‘zwan‘ },
{ hostname: ‘10.1.11.1‘, username: ‘wan‘ } ]
Now here is one of the possible solution by only using ramda.js:
var R = require("ramda"); const headerStr = `hostname, username `; const contentStr = ` 10.1.10.1, zwan 10.1.11.1, wan `; /** * Working with Ramda.js */ const stringToArray = R.compose( R.map(R.trim), R.filter(Boolean), R.split(‘,‘) ); const transformContent = R.compose( R.map(stringToArray), R.filter(Boolean), R.split(‘\n‘) ); const keyValPair = R.useWith( R.map, [ R.zipObj, R.identity ] ); const header = stringToArray(headerStr); const content = transformContent(contentStr); const res = keyValPair(header, content); console.log(res);
The solution is fine, it should be really easy to be tested and maintained. We also able to compose the logic such as :
const stringToArray = R.compose( R.map(R.trim), R.filter(Boolean), R.split(‘,‘) ); const transformContent = R.compose( R.map(stringToArray), R.filter(Boolean), R.split(‘\n‘) );
The only problem (from my point of view) is the preformence.
When we call ‘R.map, R.filter‘, it actually loop though the array twice. When we call ‘R.map(stringToArray)‘, the nested loop might increase the time complxity to O(n ^2).
Here is the transducer-js comes to help.
var t = require("transducers-js"); var R = require("ramda"); const headerStr = `hostname, username `; const contentStr = ` 10.1.10.1, zwan 10.1.11.1, wan `; /** * Working with transducer-js + Rmada.js */ const stringToArr = R.compose( t.comp( t.map(R.trim), t.filter(R.isNil) ), R.split(‘,‘) ); const transformCnt = R.compose( t.comp( t.map(stringToArr), t.filter(R.isNil) ), R.split(‘\n‘) ); const keyValPair = R.useWith( R.map, [ R.zipObj, R.identity ] ); const h = stringToArr(headerStr); const c = transformCnt(contentStr); const res2 = keyValPair(h, c); console.log("res2", res);
As you can see that, we replace
// from const stringToArray = R.compose( R.map(R.trim), R.filter(Boolean), R.split(‘,‘) ); // to const stringToArr = R.compose( t.comp( t.map(R.trim), t.filter(R.isNil) ), R.split(‘,‘) );
From ‘R.map, R.filter‘, loop twice to ‘t.comp(t.map, t.filter)‘ only loop once. ‘t.comp‘ helps to compose two transform opreation together. If you think further, this is not that easy task to combine filter‘s prediction function with map‘s transform function:
filter prediction:
f => (a → Boolean)
map transform:
f => (a → b)
Because the return type is not the same, so they cannot compose naturally. transducer-js helps us to make it easily.