Is my understanding of transducers correct? - javascript

Let's start with a definition: A transducer is a function that takes a reducer function and returns a reducer function.
A reducer is a binary function that takes an accumulator and a value and returns an accumulator. A reducer can be executed with a reduce function (note: all function are curried but I've cat out this as well as definitions for pipe and compose for the sake of readability - you can see them in live demo):
const reduce = (reducer, init, data) => {
let result = init;
for (const item of data) {
result = reducer(result, item);
}
return result;
}
With reduce we can implement map and filter functions:
const mapReducer = xf => (acc, item) => [...acc, xf(item)];
const map = (xf, arr) => reduce(mapReducer(xf), [], arr);
const filterReducer = predicate => (acc, item) => predicate(item) ?
[...acc, item] :
acc;
const filter = (predicate, arr) => reduce(filterReducer(predicate), [], arr);
As we can see there're a few similarities between map and filter and both of those functions work only with arrays. Another disadvantage is that when we compose those two functions, in each step a temporary array is created that gets passed to another function.
const even = n => n % 2 === 0;
const double = n => n * 2;
const doubleEven = pipe(filter(even), map(double));
doubleEven([1,2,3,4,5]);
// first we get [2, 4] from filter
// then final result: [4, 8]
Transducers help us solve that concerns: when we use a transducer there are no temporary arrays created and we can generalize our functions to work not only with arrays. Transducers need a transduce function to work Transducers are generally executed by passing to transduce function:
const transduce = (xform, iterator, init, data) =>
reduce(xform(iterator), init, data);
const mapping = (xf, reducer) => (acc, item) => reducer(acc, xf(item));
const filtering = (predicate, reducer) => (acc, item) => predicate(item) ?
reducer(acc, item) :
acc;
const arrReducer = (acc, item) => [...acc, item];
const transformer = compose(filtering(even), mapping(double));
const performantDoubleEven = transduce(transformer, arrReducer, [])
performantDoubleEven([1, 2, 3, 4, 5]); // -> [4, 8] with no temporary arrays created
We can even define array map and filter using transducer because it's so composable:
const map = (xf, data) => transduce(mapping(xf), arrReducer, [], data);
const filter = (predicate, data) => transduce(filtering(predicate), arrReducer, [], data);
live version if you'd like to run the code -> https://runkit.com/marzelin/transducers
Does my reasoning makes sense?

Your understanding is correct but incomplete.
In addition to the concepts you've described, transducers can do the following:
Support a early exit semantic
Support a completion semantic
Be stateful
Support an init value for the step function.
So for instance, an implementation in JavaScript would need to do this:
// Ensure reduce preserves early termination
let called = 0;
let updatesCalled = map(a => { called += 1; return a; });
let hasTwo = reduce(compose(take(2), updatesCalled)(append), [1,2,3]).toString();
console.assert(hasTwo === '1,2', hasTwo);
console.assert(called === 2, called);
Here because of the call to take the reducing operation bails early.
It needs to be able to (optionally) call the step function with no arguments for an initial value:
// handles lack of initial value
let mapDouble = map(n => n * 2);
console.assert(reduce(mapDouble(sum), [1,2]) === 6);
Here a call to sum with no arguments returns the additive identity (zero) to seed the reduction.
In order to accomplish this, here's a helper function:
const addArities = (defaultValue, reducer) => (...args) => {
switch (args.length) {
case 0: return typeof defaultValue === 'function' ? defaultValue() : defaultValue;
case 1: return args[0];
default: return reducer(...args);
}
};
This takes an initial value (or a function that can provide one) and a reducer to seed for:
const sum = addArities(0, (a, b) => a + b);
Now sum has the proper semantics, and it's also how append in the first example is defined. For a stateful transducer, look at take (including helper functions):
// Denotes early completion
class _Wrapped {
constructor (val) { this[DONE] = val }
};
const isReduced = a => a instanceof _Wrapped;
// ensures reduced for bubbling
const reduced = a => a instanceof _Wrapped ? a : new _Wrapped(a);
const unWrap = a => isReduced(a) ? a[DONE] : a;
const enforceArgumentContract = f => (xform, reducer, accum, input, state) => {
// initialization
if (!exists(input)) return reducer();
// Early termination, bubble
if (isReduced(accum)) return accum;
return f(xform, reducer, accum, input, state);
};
/*
* factory
*
* Helper for creating transducers.
*
* Takes a step process, intial state and returns a function that takes a
* transforming function which returns a transducer takes a reducing function,
* optional collection, optional initial value. If collection is not passed
* returns a modified reducing function, otherwise reduces the collection.
*/
const factory = (process, initState) => xform => (reducer, coll, initValue) => {
let state = {};
state.value = typeof initState === 'function' ? initState() : initState;
let step = enforceArgumentContract(process);
let trans = (accum, input) => step(xform, reducer, accum, input, state);
if (coll === undefined) {
return trans; // return transducer
} else if (typeof coll[Symbol.iterator] === 'function') {
return unWrap(reduce(...[trans, coll, initValue].filter(exists)));
} else {
throw NON_ITER;
}
};
const take = factory((n, reducer, accum, input, state) => {
if (state.value >= n) {
return reduced(accum);
} else {
state.value += 1;
}
return reducer(accum, input);
}, () => 0);
If you want to see all of this in action I made a little library a while back. Although I ignored the interop protocol from Cognitect (I just wanted to get the concepts) I did try to implement the semantics as accurately as possible based on Rich Hickey's talks from Strange Loop and Conj.

Related

Array reduce Unexpected use of comma operator no-sequences

I am getting an "Unexpected use of comma operator no-sequences" warning -- on the .reduce - but I am not sure how to resolve this.
const getQueryParams = () =>
this.props.location.search
.replace('?', '')
.split('&')
.reduce((r,e) => (r[e.split('=')[0]] = decodeURIComponent(e.split('=')[1]), r), {});
The code quoted uses (some would say abuses) the comma operator in order to avoid using the function body form of an arrow function. The minimal change to remove the comma operator is to put {} around the function body and do an explicit return:
const getQueryParams = () =>
this.props.location.search
.replace('?', '')
.split('&')
.reduce((r,e) => {
r[e.split('=')[0]] = decodeURIComponent(e.split('=')[1]);
return r;
}, {});
As a matter of style, though, I'd suggest not using reduce there at all. (I have a fair bit of company disliking reduce outside of Functional Programming with predefined, reusable reducers.)
In that code, the reduce is just a loop; the accumulator never changes, it's always the same object. So I'd just use a loop:
const getQueryParams = () => {
const result = {};
for (const e of this.props.location.search.replace("?", "").split("&")) {
result[e.split("=")[0]] = decodeURIComponent(e.split("=")[1]);
}
return result;
};
I'd probably also remove the redundant call to split:
const getQueryParams = () => {
const result = {};
for (const e of this.props.location.search.replace("?", "").split("&")) {
const [key, value] = e.split("=");
result[key] = decodeURIComponent(value);
}
return result;
};
Finally, both keys and values in query strings are URI-encoded, so decodeURIComponent should be used on both:
const getQueryParams = () => {
const result = {};
for (const e of this.props.location.search.replace("?", "").split("&")) {
const [key, value] = e.split("=");
result[decodeURIComponent(key)] = decodeURIComponent(value);
}
return result;
};
It'll work without if the keys are just alphanumerics and such, but it's not correct.
Stepping back from the syntax, though, you don't need to invent your own function for parsing query string parameters. Browsers already have one:
const getQueryParams = () => Object.fromEntries(
new URLSearchParams(this.props.location.search)
.entries()
);
Live Example:
const search = "?bar=Testing%201%202%203&baz=2";
console.log(
Object.fromEntries(
new URLSearchParams(search)
.entries()
)
);
You can rewrite the reduce call, so to avoid an assignment expression (and comma operator), turning the arrow function expression syntax into block syntax (see arrow function expression):
.reduce((r,e) => {
r[e.split('=')[0]] = decodeURIComponent(e.split('=')[1]);
return r;
}, {});
Another approach would be to use Object.assign:
let search = ["item=test","name=code%28","foo=%20bar"]
let result = search.reduce((r,e) =>
Object.assign(r,{[e.split('=')[0]] : decodeURIComponent(e.split('=')[1])}), {});
console.log(result)

How to deal with a `Variable 'xxx' is used before being assigned.`

I have a code block like below where I need to find something inside a loop, and also return a second variable. So I can't use a simple Array.find or Array.some good ole' for...of is my friend. map/filter don't allow a break and find can only return actual elements from the array, not a related calculation.
But the below within typescript is giving me an unavoidable error.
I'm wondering if either there's a more idiomatic way to do this, or a better structure / place to declare the variable?
Variable 'found' is used before being assigned.
let found: ParseResult
// breaks when first item found but we capture a different value
for (const rule of ParserRules) {
// const rex = new RegExp(route.match)
const parsed = rule.rex.exec(input)
if (parsed) {
found = { parsed, rule }
break
}
}
// #ts-ignore
return found // FIXME used before defined?
Here are the various JS iterator methods I tried...
const ar = [1, 2, 3, 4]
const log = console.log
const finder = (list) => {
console.log('map', list.map(it => it === 3))
console.log('find', list.find(it => it === 3))
console.log('some', list.some(it => it === 3))
console.log('filter', list.filter(it => it === 3))
console.log('find', list.find(it => {
if (it === 3) return it * 2 // value coerced to T|F
}))
console.log('filter', list.filter(it => {
if (it === 3) return it * 2 // value coerced to T|F
}))
const arr = list.forEach((k) => {
if (k === 3) return ('here')
})
log('arr', arr)
let found
for (const elem of list) {
log('elem of', elem)
if (elem === 2) {
found = elem
break
}
}
log('found', found)
}
finder(ar)
The summary of your problem is that a function returning a value in a variable when, at times, the logic doesn't get a chance to assign any value to it.
You can either initialize the variable with a default value or, at the point of returning, check if it really has a value.
let found: ParseResult= {}
OR
return found || false //Or an empty object etc
This can be done in many ways but what might suit your case would be
ar
.map((rule) => {
const parsed = rule.rex.exec(input)
if (parsed) {
return { parsed, rule }
}
})
.find((x) => !!x)
Yes you are looping it once more but this is more readable. Also it would not be that costly.
If your processing is heavy, you can try this approach as well but this will be a custom implementation and will not come out of the box:
function getParsedValue(ar, input) {
let parsed;
const rule = ar
.find((rule) => {
parsed = rule.rex.exec(input);
return !!parsed;
})
return !!rule ? { rule, parsed } : null
}

Check if a curried function is still expecting further arguments

I would like to know if it possible to find out how many remaining arguments a curried function is expecting in javascript, if possible, without ever actually calling the function.
I want a function that takes a function and returns if the function is expecting 2 or more remaining arguments.
hasSeveralRemainingArguments: fn => bool
Let's say I have the functions:
const double = x => 2*x;
const inc = x => x + 1;
const divideBy = ({dividor}) => x => x/dividor;
const decrementAndDivideBy = ({dividor}) => x => (x - 1)/dividor;
hasSeveralRemainingArguments(double); // false
hasSeveralRemainingArguments(inc); // false
hasSeveralRemainingArguments(divideBy); // true
hasSeveralRemainingArguments(divideBy({dividor: 5})); // false
hasSeveralRemainingArguments(decrementAndDivideBy); // true
hasSeveralRemainingArguments(decrementAndDivideBy({dividor: 5})); // false
The usecase would be a function foo which expects an options argument and an array of functions to call. I want to "pipe" through the array of functions and input the options argument only to the functions that are actually expecting the argument like in this case divideBy and decrementAndDivideBy, e.g.:
const pipe = (...fns) => x => fns.reduce((y, fn) => fn(y), x);
const foo = (options = {}) => (fns = []) => pipe(
fns.map(fn => (
hasSeveralRemainingArguments(fn) ?
fn(options) :
fn
)
);
const bar = (...fns) => {
const options = {
dividor: 3
}; // local, not known to the caller of bar. They just know that they can pass in a function which will receive an options-object, they just don't know what is inside this object.
return foo(options)(fns);
});
const baz = bar(
double,
inc,
divideBy,
decrementAndDivideBy
);
baz(4); // ((4*2 + 1)/3 - 1)/3 = 0.67
baz(10); // ((10*2 + 1)/3 - 1)/3 = 2
The options argument is not known by the caller of the function bar. Otherwise I could input the options argument before passing the functions into bar but this is unfortunately not possible.
You should also note that double, inc, divideBy and decrementAndDivideBy are built to only accept numbers as the argument x but this might not always be the case. If possible, I don't want to call the functions and test if the returned value is a function or not but currently I do not see any other way.
I could also pass objects with a function and a boolean "isExpectingOptions" but this would not be very nice/elegant for the person calling bar.
Do you have another idea?
Have you considered using the length property of a function?
The length property indicates the number of parameters expected by the function.
const a = curry((x, y, z) => 42);
a.length // 3
a(1).length // 2
a(1)(2).length // 1
As pointed out already,
you can check the arity of the function,
however this doesn't really work if currying is achieved manually.
const nSum = R.curry((a, b, c) => {
console.log('nSum.length', nSum.length);
return a + b + c;
});
const uSum = (a) => (b) => (c) => {
console.log('uSum.length', uSum.length);
return a + b + c;
};
nSum(1, 2, 3);
uSum(1)(2)(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js" integrity="sha256-buL0byPvI/XRDFscnSc/e0q+sLA65O9y+rbF+0O/4FE=" crossorigin="anonymous"></script>
You could potentially build a getArity function,
but this would require applying the function to extract the number of its arguments...
const getArity = (fn, arg) => {
const res = fn(arg);
return 1 + (
typeof res === 'function' ? getArity(res, arg) : 0
);
};
const uSum = (a) => (b) => (c) => a + b + c;
console.log(
getArity(uSum, 0),
);
const sum2 = uSum(0);
console.log(
getArity(sum2, 0),
);
This function, also, doesn't really tell you the arity of the function...
but how many time can be curried if we assume it as unary.

Javascript - Create a currying function returning the list of parameters

I'm trying to create a currying function returning the list of parameters but I don't how to make it.
This is my code :
const getParameters = arg => {
const parameters = [];
const innerFunction = arg => {
parameters.push(arg);
return innerFunction;
};
return innerFunction(arg);
};
getParameters(1)(2)(5); // => expected value [1,2,5]
getParameters(1)(2)(5)(20); // => expected value [1,2,5,20]
const getParameters = a => b => c => [a, b, c];
console.log(getParameters(1)(2)(5));
UPDATE: Support unlimited arguments.
PS: only limitation is when you need to end the call, you have pass last call as null or undefined or empty as shown below.
const getParameters = a => {
const parameters = [a];
const innerFunction = b => {
if (b) {
parameters.push(b);
return innerFunction;
}
return parameters;
};
return innerFunction;
};
console.log(getParameters(1)(2)(5)());
console.log(getParameters(1)(2)(5)(9)(20)(22)());
Not very good solution, but... Try this one if you want to have a string in output.
function getParameters(a) {
let parameters = '[';
parameters += `${a}`;
function innerFunction(b) {
parameters += `,${b}`;
return innerFunction;
}
innerFunction.toString = function() {
return `${parameters}]`;
};
return innerFunction;
}
console.log(getParameters(1)(3)(4)); //[1,3,4]
If you can use a final parameter(like "x") to signify end, you can use .bind
const getParameters = function(...arg) {
if (arg[arg.length - 1] !== 'x') {
return getParameters.bind(null, ...arg);
}
arg.pop();
return arg;
};
console.info(getParameters(1)(2)(5)('x'));
console.info(getParameters(1)(2)(5)(20)("x"))
As we all know world of JavaScript is a magic world, and even this thing, like infinite currying is possible
const nice = (...args) => {
return Object.assign(
nice.bind(0, ...args),
{ valueOf: () => args.reduce((a, b) => a + b, 0) }
)
}
console.log(+nice(1)()(2)) // ~> 3
console.log(+nice(1, 2)()(3)()()) // ~> 6
console.log(+nice()()()()()()(1)) // ~> 1
console.log(+nice()()()(2)()()()) // ~> 2
console.log(nice(2)()(1) + '') // ~> '3'
console.log(nice()(3)() == 3) // ~> true
console.log(nice()(3)() === 3) // ~> false
The trick is that adding unary + or using non-strict equality calls valueOf method right after all function calls, so we've got ourselves an infinite currying :)
And list currying to answer your question. It works because '' + forces toString method to be called:
const nice = (...args) => {
return Object.assign(
nice.bind(0, ...args),
{ toString: () => JSON.stringify(args) }
)
}
console.log('' + nice(1,2)(3)(4)(5)(6,7)) // ~> [1,2,3,4,5,6,7]
By the way, it's possible to make this infinite currying even more legit, because it can take any type and any amount of parameters which's really cool.
<3

Using map() on an iterator

Say we have a Map: let m = new Map();, using m.values() returns a map iterator.
But I can't use forEach() or map() on that iterator and implementing a while loop on that iterator seems like an anti-pattern since ES6 offer functions like map().
So is there a way to use map() on an iterator?
The simplest and least performant way to do this is:
Array.from(m).map(([key,value]) => /* whatever */)
Better yet
Array.from(m, ([key, value]) => /* whatever */))
Array.from takes any iterable or array-like thing and converts it into an array! As Daniel points out in the comments, we can add a mapping function to the conversion to remove an iteration and subsequently an intermediate array.
Using Array.from will move your performance from O(1) to O(n) as #hraban points out in the comments. Since m is a Map, and they can't be infinite, we don't have to worry about an infinite sequence. For most instances, this will suffice.
There are a couple of other ways to loop through a map.
Using forEach
m.forEach((value,key) => /* stuff */ )
Using for..of
var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
for (var [key, value] of myMap) {
console.log(key + ' = ' + value);
}
// 0 = zero
// 1 = one
You could define another iterator function to loop over this:
function* generator() {
for (let i = 0; i < 10; i++) {
console.log(i);
yield i;
}
}
function* mapIterator(iterator, mapping) {
for (let i of iterator) {
yield mapping(i);
}
}
let values = generator();
let mapped = mapIterator(values, (i) => {
let result = i*2;
console.log(`x2 = ${result}`);
return result;
});
console.log('The values will be generated right now.');
console.log(Array.from(mapped).join(','));
Now you might ask: why not just use Array.from instead? Because this will run through the entire iterator, save it to a (temporary) array, iterate it again and then do the mapping. If the list is huge (or even potentially infinite) this will lead to unnecessary memory usage.
Of course, if the list of items is fairly small, using Array.from should be more than sufficient.
Other answers here are... Weird. They seem to be re-implementing parts of the iteration protocol. You can just do this:
function* mapIter(iterable, callback) {
for (let x of iterable) {
yield callback(x);
}
}
and if you want a concrete result just use the spread operator ....
[...mapIter([1, 2, 3], x => x**2)]
This simplest and most performant way is to use the second argument to Array.from to achieve this:
const map = new Map()
map.set('a', 1)
map.set('b', 2)
Array.from(map, ([key, value]) => `${key}:${value}`)
// ['a:1', 'b:2']
This approach works for any non-infinite iterable. And it avoids having to use a separate call to Array.from(map).map(...) which would iterate through the iterable twice and be worse for performance.
There is a proposal, that brings multiple helper functions to Iterator: https://github.com/tc39/proposal-iterator-helpers (rendered)
You can use it today by utilizing core-js-pure:
import { from as iterFrom } from "core-js-pure/features/iterator";
// or if it's working for you (it should work according to the docs,
// but hasn't for me for some reason):
// import iterFrom from "core-js-pure/features/iterator/from";
let m = new Map();
m.set("13", 37);
m.set("42", 42);
const arr = iterFrom(m.values())
.map((val) => val * 2)
.toArray();
// prints "[74, 84]"
console.log(arr);
You could retrieve an iterator over the iterable, then return another iterator that calls the mapping callback function on each iterated element.
const map = (iterable, callback) => {
return {
[Symbol.iterator]() {
const iterator = iterable[Symbol.iterator]();
return {
next() {
const r = iterator.next();
if (r.done)
return r;
else {
return {
value: callback(r.value),
done: false,
};
}
}
}
}
}
};
// Arrays are iterable
console.log(...map([0, 1, 2, 3, 4], (num) => 2 * num)); // 0 2 4 6 8
Take a look at https://www.npmjs.com/package/fluent-iterable
Works with all of iterables (Map, generator function, array) and async iterables.
const map = new Map();
...
console.log(fluent(map).filter(..).map(..));
You could use itiriri that implements array-like methods for iterables:
import { query } from 'itiriri';
let m = new Map();
// set map ...
query(m).filter([k, v] => k < 10).forEach([k, v] => console.log(v));
let arr = query(m.values()).map(v => v * 10).toArray();
In case someone needs the typescript version:
function* mapIter<T1, T2>(iterable: IterableIterator<T1>, callback: (value: T1) => T2) {
for (let x of iterable) {
yield callback(x);
}
}
Based on the answer from MartyO256 (https://stackoverflow.com/a/53159921/7895659), a refactored typescript approach could be the following one:
function mapIterator<TIn, TOut>(
iterator: Iterator<TIn>,
callback: (input: TIn) => TOut,
): Iterator<TOut> {
return {
next() {
const result: IteratorResult<TIn> = iterator.next();
if (result.done === true) {
return result;
} else {
return {
done: false,
value: callback(result.value),
};
}
},
};
}
export function mapIterable<TIn, TOut>(
iterable: Iterable<TIn>,
callback: (input: TIn) => TOut,
): Iterable<TOut> {
const iterator: Iterator<TIn> = iterable[Symbol.iterator]();
const mappedIterator: Iterator<TOut> = mapIterator(iterator, callback);
return {
[Symbol.iterator]: () => mappedIterator,
};
}

Categories

Resources