1

Here's my compose function, as a polyfill

Function.prototype.compose = function(prevFunc) {
    var nextFunc = this;
    return function() {
        return  nextFunc.call(this, prevFunc.apply(this,arguments));
    }
}

These work:

function function1(a){return a + ' do function1 ';}
function function2(b){return b + ' do function2 ';}
function function3(c){return c + ' do function3 ';}
var myFunction = alert(function1).compose(function2).compose(function3);
myFunction('do');

var roundedSqrt = Math.round.compose(Math.sqrt)
roundedSqrt(6);

var squaredDate = alert.compose(roundedSqrt).compose(Date.parse)
quaredDate("January 1, 2014");

But this does not work!

var d = new Date();
var alertMonth = alert.compose(getMonth); <-- 
alertMonth(d);                   ^^^^

Error throws error "Uncaught ReferenceError: getMonth is not defined" in google chrome.

Now, if I try either of these instead:

var d = new Date();
function pluckMonth(dateObject) {return dateObject.getMonth();}
var alertMonth = alert.compose(pluckMonth);
var alertMonth2 = alert.compose(function(d){return d.getMonth()});
alertMonth(d);
alertMonth2(d);

They work.

Ok, so, why is that? I don't want to write extra functions, I want it to just work. The compose function uses the apply utility and just uses this for the thisArg, so it should work for object members as well as stand-alone functions, right??

i.e., these are equivalent

this.method()
method.call.apply(this)

jsFiddle: http://jsfiddle.net/kohq7zub/3/

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Dan Mantyla
  • 1,840
  • 1
  • 22
  • 33
  • 1
    `getMonth` isn't a function, it's a property of `Date.prototype`. – Barmar Nov 10 '14 at 00:41
  • Try `compose(Date.prototype.getMonth)`. – Barmar Nov 10 '14 at 00:42
  • I have, I've tried all those little tricks. I've provided a jsfiddle if you want to try yourself: http://jsfiddle.net/kohq7zub/3/ – Dan Mantyla Nov 10 '14 at 00:46
  • 1
    Yeah, I tried that myself, it didn't work. The problem is that `this` isn't propagated through the composition properly. I'm not sure that you can treat normal functions and methods equivalently like this. – Barmar Nov 10 '14 at 00:47
  • 1
    like this? http://jsfiddle.net/kohq7zub/4/ – dandavis Nov 10 '14 at 00:52
  • @dandavis yes that works! I forgot about the .bind method. Playing with it a little, this also works: `var alertMonth = alert.compose( Date.prototype.getMonth.bind(d) )` which I like a little bit more. IMO it's better than the anonymous function but not by much. Still holding out for a solution within the `compose` function. I just tried it as a stand-alone function, not a polyfill, didn't work. @dandavis, if there's no other solution than please submit that and I'll award it :) – Dan Mantyla Nov 10 '14 at 01:03
  • you can put the "re-bind" routine into your compose method, maybe behind a 2nd flag argument, so you don't have to re-write that semi-redundant bind boilerplate each usage. – dandavis Nov 10 '14 at 01:05

1 Answers1

3

If it comes to prototyping the Function object a whole bunch of libraries really do not acknowledge functions as methods.

The last entry in such arguments signatures always should be a target object, a method then can act upon. It solves the binding within the implementation; Thus avoiding to be forced later on having to use bind again and again as sole solution.

The given example slightly needs to be changed (jsFiddle) to ...

Function.prototype.compose = function(prevFunc, target) {
  var nextFunc = this;
  return function() {
    return nextFunc.call(this, prevFunc.apply(target, arguments));
  };
};

const d = (new Date);
let alertMonth;

// both variants do work.

alertMonth = alert.compose(d.getMonth, d);
alertMonth();

alertMonth = alert.compose(Date.prototype.getMonth, d);
alertMonth();

Within a next code iteration the above provided code then might result in something similar to the following implementation ...

const protoGetMonth = Date.prototype.getMonth;
let logMonth;

logMonth = ((month) => console.log(month))
  .compose(protoGetMonth, (new Date));

logMonth();

logMonth = console.log
  .compose(protoGetMonth, (new Date))
  .bind(console);

logMonth();

let alertMonth = alert.compose(protoGetMonth, (new Date));
alertMonth();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
  (function (function_prototype) {
    var
      isFunction = (function (TYPEOF_FUNCTION_TYPE) {
        return function (type) {
          return (
               (typeof type == TYPEOF_FUNCTION_TYPE)
            && (typeof type.call == TYPEOF_FUNCTION_TYPE)
            && (typeof type.apply == TYPEOF_FUNCTION_TYPE)
          );
        }
      }(typeof function_prototype)),

      getSanitizedTarget = function (target) {
        return (target == null) ? null : target;
      }
    ;
    function_prototype.compose = function (previous, target) { // compose
      var
        previousTarget = getSanitizedTarget(target),
        proceed = this
      ;
      return (isFunction(previous) && isFunction(proceed) && function () {

        return proceed.call(this, previous.apply(previousTarget, arguments));

      }) || proceed;
    };
  }(Function.prototype));
</script>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37