use anonymous function passed in object [duplicate] - javascript

I'm calling some database functions from my request handlers. All of these functions do separate "error callbacks" for handling errors. Example:
function referralComplete(req, res) {
/*getting id etc.*/
db.startDatabaseConnection(function() {
db.flagReferralAsDone(id, function(success) {
db.endDatabaseConnection();
/*doing stuff on success*/
}, onError);
}, onError);
function onError(err, description) {
logger.error(description + ": " + err);
user.pageNotFound(req, res);
}
}
I have several request handlers similar to this all calling different database functions. At the moment I have duplicated onError() into the scope of each of them since I need req and res when handling the error, but I like think that there might be a way to achieve the same without the duplication.
So the question is, would it be possible to somehow bind req and res to onError() so that I won't have to duplicate onError() into each request handler?

Binding is simple!
db.startDatabaseConnection(function(){
// whatever
}, onError.bind(this, var1, var2));
You can learn more about binding by clicking this awesome link, even though the link is sort of long.
Here's a real basic demo
// a function
var something = function (a, b, c) {
console.log(a, b, c);
};
// a binding of something with 3 defined args
var b = something.bind(null, 1, 2, 3);
// call b
b();
//=> 1 2 3
Behind the scenes, this is basically what's happening
// ES6
const myBind = (f, context, ...x) =>
(...y) => f.call(context, ...x, ...y);
// ES5
var myBind = function(fn, context) {
var x = Array.prototype.slice.call(arguments, 2);
return function() {
var y = Array.prototype.slice.call(arguments, 0);
return fn.apply(context, x.concat(y));
};
};
var b = myBind(console.log, console, 1, 2, 3);
b();
// => 1 2 3
b(4,5,6)
// => 1 2 3 4 5 6
Context?
Context allows you to dynamically change the this of your function. Note you can only bind the context of functions defined with the function keyword; arrow functions have a lexical this that cannot be manipulated. This is shown for sake of completeness, but I do advise against this kind of program. It's usually better to just use another function parameter instead of relying on dynamic function context, this. Supporting context switching like this is to enable object-oriented style in JavaScript. Unless you are using this style, I see no reason to pay attention to context.
const getCanvas = (id) =>
document.getElementById(id).getContext('2d')
const draw = function (canvas, x = 0, y = 0)
{ canvas.beginPath()
canvas.strokeStyle = this.color // `this` refers to context!
canvas.rect(x, y, this.width, this.height) // `this` refers to context!
canvas.stroke()
}
// create two contexts
const contextA =
{ color: 'blue', width: 10, height: 10 }
const contextB =
{ color: 'green', width: 10, height: 20 }
// bind the draw function to each context and the canvas
const drawA =
draw.bind(contextA, getCanvas('main'))
const drawB =
draw.bind(contextB, getCanvas('main'))
// call the bound drawing functions normally
// draw three blue squares
drawA(0, 0)
drawA(20, 0)
drawA(40, 0)
// and one green rect
drawB(80, 0)
<canvas id="main"></canvas>
Partial application
Similar to binding is Partial Application
In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
Here we could make a very simple partial helper procedure which helps us accomplish this
const identity = x =>
x
const partial = (f = identity, ...x) =>
(...y) => f (...x, ...y)
const foo = (...all) =>
console.log ('array of', all)
partial (foo, 1, 2, 3) (4, 5, 6)
// 'array of', [ 1, 2, 3, 4, 5, 6 ]
Currying
Currying is related to, but not the same as, binding or partial application
In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.
const identity = x =>
x
const curry = (f = identity, arity = f.length) => x =>
{
const next = (xs = []) =>
xs.length >= arity
? f (...xs)
: x => next ([ ...xs, x ])
return next ([ x ])
}
const foo = (a, b, c) =>
console.log ('a', a, 'b', b, 'c', c)
curry (foo) (1) (2) (3)
// 'a' 1 'b' 2 'c' 3
curry (foo) ('choo') ('bye') ()
// 'a' 'choo' 'b' 'bye' 'c' undefined

Related

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.

How can a function nested in an object knows its path?

I want to be able to call fo and generate a string from within (the callee) that represents its "object address" or path e.g. "example.fee.set.fo".
Would this require a function that iterates over the "root" object (i.e example)?
The purpose is to reference who’s calling to the server side.
let example={
fee:{
set:{
fo(){
myName=????? //Here: generate a string with the value "example.fee.set.fo"
$.ajax({data:{WhosCalling:myName,foo:'bar'}});
},
}
}
}
Build a function that takes an object and the "root address" and recursively goes inside it, collecting the path to each nested function along the way.
Then wrap each of these nested functions with a function that accepts their arguments, apply them and return its address.
My recommendation would be that you keep your nested functions intact. But wrap them in a function that both executes them and talk to your server.
Example:
const communicate = (path, fn) =>
(...args) => {
const addr = path.join('.');
const exec = fn(...args);
console.log(`Call from ${addr}: ${exec}`); // or $.ajax()
return exec;
};
const baz = x => x + 10;
const baz_with_communicate = communicate(['foo', 'bar', 'baz'], baz);
baz_with_communicate(10);
//=>LOG: "Call from foo.bar.baz: 20"
//=> 20
So now you just don't do the "function with communicate" business, this is done for you automatically in the spy function.
Judging by your comments you seem to be struggling with the concept of functions taking functions as parameters and returning other functions. It is however a powerful concept that you should get familiar with as it would help you design powerful abstractions.
⚠️ This won't run on Edge (uses Object.fromEntries which is not supported yet)
const communicate = (path, fn) =>
(...args) => {
const addr = path.join('.');
const exec = fn(...args);
console.log(`Call from ${addr}: ${exec}`); // or $.ajax()
return exec;
};
const spy = (o, ...path) =>
Object.fromEntries(
Object
.entries(o)
.map(([k, v]) =>
v !== null && typeof v === 'object'
? [ k
, spy(v, ...path.concat(k))
]
: typeof v === 'function'
? [ k
, communicate(path.concat(k), v)
]
: [ k
, v
]));
const o_spy = spy(o, 'example');
console.log(o_spy.foo.bar.baz(5));
console.log(o_spy.bar.baz.bat(5));
<script>
const o =
{ foo:
{ bar:
{ baz: x => x + 10 }}
, bar:
{ baz:
{ bat: x => x + 20 }}};
</script>
Here's how I solved it!
let example={
fee:{
set:{
fo(){ let myName = arguments.callee.address;
console.log(myName); //I'm now able to get the location name of this function
//I can now use this to give context & identify myself to the server
//IE $.ajax({data:{WhosCalling:myName,foo:'bar'}});
},
}}};
function fn_addresses(obj,loc){
$.each(obj,function(k,v){ let _loc=(loc?loc+'.':'')+k; //#.each requires jQuery -- pure js: for(var i in this)
if(typeof v=='object'){ return fn_addresses(v,_loc) } //iterate through objects
//↓ Below binds the address (ie 'example.fee.set.fo') to the functions namespace ↓
if(typeof v=='function'){ v.address=_loc; }
});
}
fn_addresses(example,'example'); //traverse the object adding 'address' to nested functions
example.fee.set.fo();
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>

how the bind() method works in association with the "this" keyword? [duplicate]

I'm calling some database functions from my request handlers. All of these functions do separate "error callbacks" for handling errors. Example:
function referralComplete(req, res) {
/*getting id etc.*/
db.startDatabaseConnection(function() {
db.flagReferralAsDone(id, function(success) {
db.endDatabaseConnection();
/*doing stuff on success*/
}, onError);
}, onError);
function onError(err, description) {
logger.error(description + ": " + err);
user.pageNotFound(req, res);
}
}
I have several request handlers similar to this all calling different database functions. At the moment I have duplicated onError() into the scope of each of them since I need req and res when handling the error, but I like think that there might be a way to achieve the same without the duplication.
So the question is, would it be possible to somehow bind req and res to onError() so that I won't have to duplicate onError() into each request handler?
Binding is simple!
db.startDatabaseConnection(function(){
// whatever
}, onError.bind(this, var1, var2));
You can learn more about binding by clicking this awesome link, even though the link is sort of long.
Here's a real basic demo
// a function
var something = function (a, b, c) {
console.log(a, b, c);
};
// a binding of something with 3 defined args
var b = something.bind(null, 1, 2, 3);
// call b
b();
//=> 1 2 3
Behind the scenes, this is basically what's happening
// ES6
const myBind = (f, context, ...x) =>
(...y) => f.call(context, ...x, ...y);
// ES5
var myBind = function(fn, context) {
var x = Array.prototype.slice.call(arguments, 2);
return function() {
var y = Array.prototype.slice.call(arguments, 0);
return fn.apply(context, x.concat(y));
};
};
var b = myBind(console.log, console, 1, 2, 3);
b();
// => 1 2 3
b(4,5,6)
// => 1 2 3 4 5 6
Context?
Context allows you to dynamically change the this of your function. Note you can only bind the context of functions defined with the function keyword; arrow functions have a lexical this that cannot be manipulated. This is shown for sake of completeness, but I do advise against this kind of program. It's usually better to just use another function parameter instead of relying on dynamic function context, this. Supporting context switching like this is to enable object-oriented style in JavaScript. Unless you are using this style, I see no reason to pay attention to context.
const getCanvas = (id) =>
document.getElementById(id).getContext('2d')
const draw = function (canvas, x = 0, y = 0)
{ canvas.beginPath()
canvas.strokeStyle = this.color // `this` refers to context!
canvas.rect(x, y, this.width, this.height) // `this` refers to context!
canvas.stroke()
}
// create two contexts
const contextA =
{ color: 'blue', width: 10, height: 10 }
const contextB =
{ color: 'green', width: 10, height: 20 }
// bind the draw function to each context and the canvas
const drawA =
draw.bind(contextA, getCanvas('main'))
const drawB =
draw.bind(contextB, getCanvas('main'))
// call the bound drawing functions normally
// draw three blue squares
drawA(0, 0)
drawA(20, 0)
drawA(40, 0)
// and one green rect
drawB(80, 0)
<canvas id="main"></canvas>
Partial application
Similar to binding is Partial Application
In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
Here we could make a very simple partial helper procedure which helps us accomplish this
const identity = x =>
x
const partial = (f = identity, ...x) =>
(...y) => f (...x, ...y)
const foo = (...all) =>
console.log ('array of', all)
partial (foo, 1, 2, 3) (4, 5, 6)
// 'array of', [ 1, 2, 3, 4, 5, 6 ]
Currying
Currying is related to, but not the same as, binding or partial application
In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.
const identity = x =>
x
const curry = (f = identity, arity = f.length) => x =>
{
const next = (xs = []) =>
xs.length >= arity
? f (...xs)
: x => next ([ ...xs, x ])
return next ([ x ])
}
const foo = (a, b, c) =>
console.log ('a', a, 'b', b, 'c', c)
curry (foo) (1) (2) (3)
// 'a' 1 'b' 2 'c' 3
curry (foo) ('choo') ('bye') ()
// 'a' 'choo' 'b' 'bye' 'c' undefined

Can this code be refactored to a more functional programming style using hof/compose/currying/partial application/monads?

I've just started learning about functional programming and I'm trying to put into practice what I've learned. I've got this code below and I just don't know where can I apply function composition, partial application in this function.
Any ideas how to refactor this using the functional techniques?
function compareCodes(validateFn, moreProcessingFn, doStuffOnCodeAFn, doStuffOnCodeBFn, doSomething1Fn, doSomething2Fn, codeA, codeB, param1, param2) {
let result = null;
if (validateFn(codeA, codeB)) {
const isCodeAValid = doStuffOnCodeAFn(codeA); // returns a boolean
const isCodeBValid = doStuffOnCodeBFn(codeB); // returns a boolean
const isItAMatch = moreProcessingFn(isCodeAValid, isCodeBValid, codeA, codeB); // returns a boolean
if (isItAMatch) {
result = doSomething1Fn (param1, param2);
} else {
result = doSomething2Fn (param1, param2);
}
}
return result;
}
The first step would be to get rid of all the helper variables. While the boolean intermediate variables ease understanding with their descriptive names, at least result is totally unnecessary.
function compareCodes(validateFn, moreProcessingFn, doStuffOnCodeAFn, doStuffOnCodeBFn, doSomething1Fn, doSomething2Fn, codeA, codeB, param1, param2) {
return validateFn(codeA, codeB)
? (moreProcessingFn(doStuffOnCodeAFn(codeA), doStuffOnCodeBFn(codeB), codeA, codeB)
? doSomething1Fn
: doSomething2Fn
)(param1, param2)
: null;
}
Next you could apply some currying (you could do it per parameter, but I think it's more useful in the blocks that will likely be used together):
function compareCodes(validateFn, moreProcessingFn, doStuffOnCodeAFn, doStuffOnCodeBFn, doSomething1Fn, doSomething2Fn) {
return function(codeA, codeB) {
return validateFn(codeA, codeB)
? moreProcessingFn(doStuffOnCodeAFn(codeA), doStuffOnCodeBFn(codeB), codeA, codeB)
? doSomething1Fn
: doSomething2Fn
: function(param1, param2) { return null; };
};
}
But that's about it. While it would be possible to write your own combinators for the conditionals and the parallel feeding of multiple arguments into multiple functions, you will not gain anything in the process. There certainly are no standard combinators like composition that would help you out here.
It might be a different thing if you drop out of always supplying two things together (A and B, 1 and 2) but as distinct parameters. If you instead modify all of your functions to take tuples instead (here represented as arrays of length 2, given JavaScript's lack of a pair type), we can do something. First we convert from
function compareCodes(validateFn, moreProcessingFn, [doStuffOnCodeAFn, doStuffOnCodeBFn], [doSomething1Fn, doSomething2Fn], [codeA, codeB], [param1, param2]) {
return validateFn([codeA, codeB])
? (moreProcessingFn([doStuffOnCodeAFn(codeA), doStuffOnCodeBFn(codeB)], [codeA, codeB])
? doSomething1Fn
: doSomething2Fn
)([param1, param2])
: null;
}
to (I'm using ES6 syntax, notable arrow functions and destructuring)
const bimap = ([f, g]) => ([x, y]) => [f(x), g(y)];
const fst = ([x, _]) => x;
const snd = ([_, y]) => y;
function compareCodes(validate, moreProcessing, doStuff, doSomething, code, param) {
return validate(code)
? (moreProcessing(bimap(doStuff)(code), code)
? fst
: snd
)(doSomething)(param)
: null;
}
Now that is something we can indeed tackle with combinators:
const compose = f => g => x => f(g(x));
const bind = f => g => x => f(g(x), x);
const cond = pred => then => other => x => pred(x) ? then(x) : other(x);
const k = x => _ => x;
function compareCodes(validate, moreProcessing, doStuff, doSomething)
return cond(validate,
cond(bind(moreProcessing)(compose(bimap)(doStuff)),
fst(doSomething),
snd(doSomething)
),
k(k(null))
);
}
We could go further to a completely point-free definition of compareCodes, but honestly that's not worth it.

How to clone ES6 generator?

I'm trying to create a List monad in ES6 using generators. To make it work I need to create a copy of an iterator that has already consumed several states. How do I clone an iterator in ES6?
function* test() {
yield 1;
yield 2;
yield 3;
}
var x = test();
console.log(x.next().value); // 1
var y = clone(x);
console.log(x.next().value); // 2
console.log(y.next().value); // 2 (sic)
I've tried clone and cloneDeep from lodash, but they were of no use. Iterators that are returned in this way are native functions and keep their state internally, so it seems there's no way to do it with own JS code.
Iterators […] keep their state internally, so it seems there's no way
Yes, and that for a good reason. You cannot clone the state, or otherwise you could tamper too much with the generator.
It might be possible however to create a second iterator that runs alongside of the first one, by memorizing its sequence and yielding it later again. However, there should be only one iterator that really drives the generator - otherwise, which of your clones would be allowed to send next() arguments?
I wrote a do-notation library for JavaScript, burrido. To get around the mutable generator problem I made immutagen, which emulates an immutable generator by maintaining a history of input values and replaying them to clone the generator at any particular state.
You can't clone a generator--it's just a function with no state. What could have state, and therefore what could be cloned, is the iterator resulting from invoking the generator function.
This approach caches intermediate results, so that cloned iterators can access them if necessary until they "catch up". It returns an object which is both an iterator and an iterable, so you can either call next on it or for...of over it. Any iterator may be passed in, so you could in theory have cloned iterators over an array by passing in array.values(). Whichever clone calls next first at a given point in the iteration will have the argument passed to next, if any, reflected in the value of the yield in the underlying generator.
function clonableIterator(it) {
var vals = [];
return function make(n) {
return {
next(arg) {
const len = vals.length;
if (n >= len) vals[len] = it.next(arg);
return vals[n++];
},
clone() { return make(n); },
throw(e) { if (it.throw) it.throw(e); },
return(v) { if (it.return) it.return(v); },
[Symbol.iterator]() { return this; }
};
}(0);
}
function *gen() {
yield 1;
yield 2;
yield 3;
}
var it = clonableIterator(gen());
console.log(it.next());
var clone = it.clone();
console.log(clone.next());
console.log(it.next());
Obviously this approach has the problem that it keeps the entire history of the iterator. One optimization would be to keep a WeakMap of all the cloned iterators and how far they have progressed, and then clean up the history to eliminate all the past values that have already been consumed by all clones.
Thanks for the comments on my previous question. Inspired by those and some of the answers here I've made a cloneable_generator_factory to solve the problem:
function cloneable_generator_factory (args, generator_factory, next_calls = [])
{
let generator = generator_factory(args)
const cloneable_generator = {
next: (...args) =>
{
next_calls.push(args)
return generator.next(...args)
},
throw: e => generator.throw(e),
return: e => generator.return(e),
[Symbol.iterator]: () => cloneable_generator,
clone: () =>
{
// todo, use structuredClone when supported
const partial_deep_cloned_next_args = [...next_calls].map(args => [...args])
return cloneable_generator_factory(args, generator_factory, partial_deep_cloned_next_args)
},
}
// Call `generator` not `cloneable_generator`
next_calls.forEach(args => generator.next(...args))
return cloneable_generator
}
// Demo
function* jumpable_sequence (args) {
let i = args.start
while (true)
{
let jump = yield ++i
if (jump !== undefined) i += jump
}
}
let iter = cloneable_generator_factory({ start: 10 }, jumpable_sequence)
console.log(iter.next().value) // 11
console.log(iter.next(3).value) // 15 (from 11 + 1 + 3)
let saved = iter.clone()
console.log("Saved. Continuing...")
console.log(iter.next().value) // 16
console.log(iter.next(10).value) // 27 (from 16 + 1 + 10)
console.log("Restored")
iter = saved
console.log(iter.next().value) // 16
console.log(iter.next().value) // 17
console.log(iter.next().value) // 18
For those using TypeScript, here's a link to the playground of the following code:
interface CloneableGenerator <A, B, C> extends Generator<A, B, C>
{
clone: () => CloneableGenerator <A, B, C>
}
function cloneable_generator_factory <R, A, B, C> (args: R, generator_factory: (args: R) => Generator<A, B, C>, next_calls: ([] | [C])[] = []): CloneableGenerator<A, B, C>
{
let generator = generator_factory(args)
const cloneable_generator: CloneableGenerator<A, B, C> = {
next: (...args: [] | [C]) =>
{
next_calls.push(args)
return generator.next(...args)
},
throw: e => generator.throw(e),
return: e => generator.return(e),
[Symbol.iterator]: () => cloneable_generator,
clone: () =>
{
// todo, use structuredClone when supported
const partial_deep_cloned_next_args: ([] | [C])[] = [...next_calls].map(args => [...args])
return cloneable_generator_factory(args, generator_factory, partial_deep_cloned_next_args)
},
}
// Call `generator` not `cloneable_generator` to avoid args for `next` being multiplied indefinitely
next_calls.forEach(args => generator.next(...args))
return cloneable_generator
}
// Demo
function* jumpable_sequence (args: {start: number}): Generator<number, number, number | undefined> {
let i = args.start
while (true)
{
let jump = yield ++i
if (jump !== undefined) i += jump
}
}
let iter = cloneable_generator_factory({ start: 10 }, jumpable_sequence)
console.log(iter.next().value) // 11
console.log(iter.next(3).value) // 15 (from 11 + 1 + 3)
let saved = iter.clone()
console.log("Saved. Continuing...")
console.log(iter.next().value) // 16
console.log(iter.next(10).value) // 27 (from 16 + 1 + 10)
console.log("Restored")
iter = saved
console.log(iter.next().value) // 16
console.log(iter.next().value) // 17
console.log(iter.next().value) // 18
You could do something like is provided in Python itertools.tee, i.e. let a function return multiple iterators that take off from where the given iterator is at.
Once you call tee, you should no longer touch the original iterator, since tee is now managing it. But you can continue with the 2 or more "copies" you got back from it, which will have their independent iterations.
Here is how that function tee can be defined, with a simple example use of it:
function tee(iter, length=2) {
const buffers = Array.from({length}, () => []);
return buffers.map(function* makeIter(buffer) {
while (true) {
if (buffer.length == 0) {
let result = iter.next();
for (let buffer of buffers) {
buffer.push(result);
}
}
if (buffer[0].done) return;
yield buffer.shift().value;
}
});
}
// Demo
function* naturalNumbers() {
let i = 0;
while (true) yield ++i;
}
let iter = naturalNumbers();
console.log(iter.next().value); // 1
console.log(iter.next().value); // 2
let saved;
[iter, saved] = tee(iter);
console.log("Saved. Continuing...");
console.log(iter.next().value); // 3
console.log(iter.next().value); // 4
console.log("Restored");
iter = saved;
console.log(iter.next().value); // 3
console.log(iter.next().value); // 4
console.log(iter.next().value); // 5

Categories

Resources