views:

114

answers:

3

I was working on some code earlier today, when I realized, "Hey! This code would be more concise and semantic if I abstracted the idea of a boolean not out of an anonymous function and into a prototype function..."

Consider a predicate generator:

function equalTo(n) {
    return function(x) {
        return n==x;
    };
}

So you can use it like so:

[1,2,3,4,1,2,3,4].filter(equalTo(2)) == [2,2]

Now, my idea is to make a predicate "inverser":

Function.prototype.not = function() {
    //???
}

So that you can say:

[1,2,3,4,1,2,3,4].filter(equalTo(2).not) == [1,3,4,1,3,4]

My first stab at the implementation was probably very naive:

Function.prototype.not = function () {
    return ! this(arguments);
}

And probably why it didn't work.

How would you implement this function, and why?

I'm just trying to wrap my head around functional ideas, and know JavaScript well enough to know it can be used to do this, but just not how.

+5  A: 

Your implementation won't work for several reasons:

  • You need to return a function, not a boolean.
  • You should pass the arguments as-is, not wrapped in an array.
  • You should preserve the context (this keyword) that the function would have been called in.

I would implement it like this:

Function.prototype.not = function (context) {
    var func = this;
    return function() { return !func.apply(context || this, arguments); };
}
  • I return an anonymous function (function() { ... })
  • I call apply to call the original function in the current contexts with the actual arguments.
  • (EDIT) Free bonus: I added an optional context parameter which will override this for the callback.
SLaks
Very enlightening. Thank you.
Austin Hyde
+1, very well-done
Rex M
And as `not` is a function, you call it using `equalTo(2).not()`.
Guffa
It would be better to assign the returned function as the prototype so you can call equalTo.not() instead of equalTo.not()() - e.g. Function.prototype.not = (function (context) { ...})();
Luke Schafer
@Luke: That won't work, because the context at the call site will be wrong. (When the function in your prototype is called, you won't be able to know which function the property was accessed for) You could do it with `Object.defineProperty`, though.
SLaks
@SLaks - I didn't read it all properly so my bad. The point is that with a rework, it won't need the extra invocation - something like proto.not = function() { return this == window ? false : !((this).apply(this, arguments)); }; - there is no need to accept the context as an argument as far as I can see
Luke Schafer
@SLaks - 'not' is prototyped to the anonymous function that is returned from equalTo, so mapping equalTo(2).not would work. The problem I think is with my original example thinking the usage would be equalTo.not(2) but it's not, it would be, in a straight call, equalTo(2).not(variableToCheck). The context of the anon function remains the same
Luke Schafer
@SLaks - not sure why you removed your last comment, I hope it's because you're rephrasing it. It was an enjoyable debate regardless of who's right, and I'm sure anyone reading will have something to learn :)
Luke Schafer
@SLaks - and I do believe you're right :)
Luke Schafer
@Luke: I did not remove any comment. You're wrong because he wants to save the inverted function for later use in a different context. Look at the two different uses of `this` in your own code.
SLaks
+2  A: 

I would probably do it like so (but perhaps with some sort of namespacing):

function not (x) {
  return !x;
}

function id (x) {
  return x;
}

function compose (/*funcs*/) {
  var args = arguments.length
    ? Array.prototype.slice.call (arguments)
    : [id]
    ;
  return function () {
    var val = args [args.length - 1].apply (null, arguments);
    for (var i = args.length - 2; i >= 0; --i) {
      val = args [i] (val);
    }
    return val;
  };
}

[1,2,3,4,1,2,3,4].filter (compose (not, equalTo (2)));
trinithis
I like this method. It is less OOPish and more flexible.
Austin Hyde
+1  A: 

Using your idea:

function equalTo(n) {
    var fn = function(x) {
        return n == x;
    };
    fn.not = function(x) {
        return n != x; // use this for simpler conditions
        return !fn.apply(this, arguments); // use this for more complex logic
    }
    return fn;
}

So your example would work:

[1,2,3,4,1,2,3,4].filter(equalTo(2).not) == [1,3,4,1,3,4]

Edit: You can write a helper function (better name to be found) so not doesn't need to be redefined every time:

function generateFnWithNot(fn) {
    return function () {
        var f = fn.apply(this, arguments);
        f.not = function () {
            return !f.apply(this, arguments);
        }
        return f;
    };
}

So when you're defining your functions, you can define them as normal with the exception of wrapping a generateFnWithNot call:

var equalTo = generateFnWithNot(function (n) {
    return function (x) {
        return n == x;
    };
});

equalTo(5) // resolves to function () { return n == 5; }
equalTo(5).not // resolves to function () { return n != 5; } 
Casey Hope
This is an interesting idea, in that `not` is scoped to `equalTo`. The only stumbling block is that it would not compose well with other functions, as you would have to re-define `not` for each one.
Austin Hyde
I edited my answer to include a helper function that will add `not` to a generic function, so now `not` doesn't need to be redefined for each function -- only a function call is required.
Casey Hope