ciso
ciso

Reputation: 3050

How to replace closure variables with additional function call arguments to avoid constantly re-creating functions?

Given:

list = [ "one", "something", "other" ];
foo = function () {
    var str = "something", 
        bar = function (el, idx, array) {return el === str;};
    return list.some(bar);
}; 
for (var i = 0; i < 10; i += 1) {
   foo(); // true
}

bar is called by list.some() with three arguments.

If I want define bar outside of foo then I have to give it str in a closure like this:

list = [ "one", "something", "other" ];
bar = function (str) {
    return function baz(el, idx, array) {return el === str;};
}
foo = function () {
    var str = "something"; 
    return list.some(bar(str));
}; 
for (var i = 0; i < 10; i += 1) {
   foo(); // true
}

So with every call to bar, a function baz is created / defined within bar. Can the closure and constant function re-creation be avoided by adding an extra
argument to the list.some() call of bar like this:

list = [ "one", "something", "other" ];
bar = function (el, idx, array, str) {return el === str;};
// Do something to bar's argument list so it's called with more than three arguments?
baz = function (str) {return bar(el, idx, array, str);}; // ????
foo = function () {
    var str = "something";
    return list.some(baz(str));
};
for (var i = 0; i < 10; i += 1) {
   foo(); // true
}

Updated: Note that foo can be called from within a loop as in the above examples. Can the recreation of all the functions be avoided such that each function is only created once?

I would like to pass bar four arguments (or any number of arguments after the first three, like this bar = function (el, idx, array, str, ...). The goal being to create any one function only once. No outside libraries, please.

Upvotes: 1

Views: 503

Answers (3)

mhodges
mhodges

Reputation: 11116

So, you can do this using a functional programming approach called Currying (Helpful article). I think you could get technical and say that ramda is creating another function under the covers, which is true, however, it defines and returns the function 1 time, rather than for each iteration, which I believe is what you were aiming to accomplish.

The code would look like the following:

var ramda = R; // alias ramda library from default R for clarity
list = [ "one", "something", "other" ];
var bar = ramda.curry(function (str, el, idx, array) {
    return el === str;
});
var foo = (function (array) {
    var str = "something", 
    // bar(str) creates a function one time that is passed to .some(),
    // rather than for each iteration
    foundIt = array.some(bar(str)); 
    return foundIt;
}(list));

console.log(foo);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.23.0/ramda.js"></script>

Keep in mind, the use of the ramda library specifically is not necessary, as you can implement currying yourself with vanilla JS, or with other common libraries (like lodash).

As per request - to curry a function yourself in JS, it would look like the following: (Copied from this article)

function curry(fx) {
  var arity = fx.length;

  return function f1() {
    var args = Array.prototype.slice.call(arguments, 0);
    if (args.length >= arity) {
      return fx.apply(null, args);
    }
    else {
      return function f2() {
        var args2 = Array.prototype.slice.call(arguments, 0);
        return f1.apply(null, args.concat(args2)); 
      }
    }
  };
}

Upvotes: 1

Diego
Diego

Reputation: 816

It could be:

var list = [ "one", "something", "other" ];
var bar = function (el, idx, array) { return el === this.str; };
var foo = (function () {
    var str = "something"; 
    return list.some(bar.bind({str}));
}()); 
console.log(foo)

But I don't know if i have really understood you.

Upvotes: 2

Joe
Joe

Reputation: 82584

Using Function.bind will work setting the context, this, to the string. However, function.bind still creates a new function. Really, no way to create a closure without a function. They are dependent on each other.

I think this is the cleanest approach:

list = [ "one", "something", "other" ];
bar = function indexAndStringMatch(key) {
    return function (el, idx, array) { return el === key && idx; };
}
foo = (function () {
    var str = "something", 
        foundIt = array.some(bar(str));
}());

Upvotes: 2

Related Questions