3

I want to implement a higher order function (hof) that essentially works like F# style forward-pipe operator (passes a value as the first argument to another function, myFunc). The only way I can think of is this:

function hof(val, myFunc, args_array) {...}

where args_array is the array of arguments for the call to myFunc (excluding the first argument, since that's going to be val)

But this doesn't look very elegant to me. Is there a better way to do this?

Edit: I found this on github https://gist.github.com/aaronpowell/d5ffaf78666f2b8fb033. But I don't really understand what the sweet.js code is doing. It'd be very helpful if you could annotate the code, specifically:

case infix { $val | _ $fn($args (,) ...) } => {
    return #{
        ($fn.length <= [$args (,) ...].length + 1 ? $fn($args (,) ..., $val) : $fn.bind(null, $args (,) ..., $val))
    }
}

case infix { $val | _ $fn } => {
    return #{
        ($fn.length <= 1 ? $fn($val) : $fn.bind(null, $val))
    }
}
tldr
  • 11,924
  • 15
  • 75
  • 120
  • You can't really do this with the syntax you provide because `hof()` never sees the `myFunc` function object; you are invoking `myFunc` directly and passing the return value to `hof`. – cdhowie Sep 09 '14 at 05:40
  • It's not really clear what your objective is. It would help if you could give us a concrete example. At the moment, it sounds a lot like an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – JLRishe Sep 09 '14 at 05:51
  • yep, I realize that. I modified the title/tags. Any suggestions? – tldr Sep 09 '14 at 06:45
  • @tldr Well, what do you think of the answers so far? There's some decent stuff down there, and I think taking on a whole new framework just for this one feature might be a bit much. I don't know anything about Sweet.js and can't help you there. And you shouldn't completely change the scope of your question after you've already received 7 answers. Please open a new question if you want to ask about macros. – JLRishe Sep 09 '14 at 06:54

8 Answers8

1

If you want something like the F# pipeline operator, I think your best bet is either this approach that you had in your post:

hof(val, myFunc, [arg1, arg2, arg3]);

or this:

hof(val, myFunc, arg1, arg2, arg3);

The first one can be implemented like this:

function hof(val, func, args) {
    func.apply(this, [val].concat(args || []));
}

The second one can be implemented like this:

function hof(val, func) {
    func.apply(this, [val].concat(Array.prototype.slice(arguments, 2));
}

But that all leaves the question of why you wouldn't just call the function in a normal way:

myFunc(val, arg1, arg2, arg3);
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • I have edited the question to clarify what I'm trying to do. Please let me know if it's still not clear. – tldr Sep 09 '14 at 05:52
1

Isn't this called Currying?

Anyhow, here's a rubbish example, I'm sure there are better examples if you search:

function myCurry() {
    var args = [].slice.call(arguments);
    var fn = args.splice(0,1)[0];
    return function(arg) {
      var a = [].concat(arg, args);
      return fn.apply(this, a);
    };
}

// Just sums the supplied arguments, initial set are 1,2,3
var fn = myCurry(function(){
                   var sum = 0;
                   for (var i=0, iLen=arguments.length; i<iLen; i++) {

                     // Show args in sequence - 4, 1, 2, 3
                     console.log('arguments ' + i + ': ' + arguments[i]);
                     sum += arguments[i];
                   }
                   return sum;
                }, 1,2,3);

// Provide an extra argument 4
console.log(fn(4)); // 10
RobG
  • 142,382
  • 31
  • 172
  • 209
  • Wow! I didn't even know about currying. So I can define the higher order function like so: hof(val, fn) {return fn(val)}, and call it like so: hof(val, curry(myFunc)(arg1, arg2, arg3)). That is indeed a much more elegant solution! – tldr Sep 09 '14 at 07:25
  • @tldr Still not sure I see the point of having `hof` at all. Why not just use `curry(myFunc)(arg1, arg2, arg3)(val)`, or better yet, why not just use `myFunc(val, arg1, arg2, arg3)` in the first place? – JLRishe Sep 09 '14 at 09:23
  • because I want the hofs to be chainable, they pipe the result of the last operation to the next myFunc. The way you describe it, it's not chainable. – tldr Sep 09 '14 at 14:37
1

Sweet.js macros are simple, they only look cryptic at first sight.

A forward pipe in F# language, infix operator |> applies function on the right to the result of the expression on the left.

New syntax should look like this(|> macro):

var seven = 7;
var outcome = 3 |> plus(seven) |> divideBy(2); // outcome must be 5

The pipe macro must append argument to the function, so that after expansion functions would look like this: plus(seven, 3).

The sweet.js macro in the question works in simplest cases.

Main part is in this snippet return #{ /** macro expansion template */ }. It contains javascript that is executed in compile time(sweet.js) returning text that is a code for a macro expansion. The result of compilation:

var outcome$633 = plus.length <= [seven].length + 1 ? plus(seven, 5) : plus.bind(null, seven, 5);

The length of a function in js is its number of expected positional arguments.

A sofisticated macro that allows using functions contained in objects(dot notation):

macro (|>) {
  case infix { $val | _ $fn(.)... ( $args(,)... ) } => {
    return #{
      ($fn(.)....length <= [$args(,)...].length + 1 ? $fn(.)...( $args(,)..., $val) : $fn(.)....bind(null, $args(,)..., $val))
    }
  }

  case infix { $val | _ $fn($args ...) } => {
    return #{
      ($fn.length <= [$args ...].length + 1 ? $fn($args ..., $val) : $fn.bind(null, $args ..., $val))
    }
  }

  case infix { $val | _ $fn(.)... } => {
    return #{
      ($fn(.)....length <= 1 ? $fn(.)...($val) : $fn(.)....bind(null, $val))
    }
  }
}

One restriction/requirement of this macro is to omit parens () if a function in a pipeline does not take arguments.

Line var outcome = 5 |> plus(seven) |> obj.subObj.func; expands to this nested ternary operator expression in js:

var outcome$640 = obj.subObj.func.length <= 1 ? obj.subObj.func(plus.length <= [seven].length + 1 ? plus(seven, 5) : plus.bind(null, seven, 5)) : obj.subObj.func.bind(null, plus.length <= [seven].length + 1 ? plus(seven, 5) : plus.bind(null, seven, 5));

1

In ES6, this becomes quite compact:

var hof = (fn, ...params) => (...addtl) => fn(...parms, ...addtl);
0

You can easily use a closure for this kind of wrapping:

function callf(f, count) {
    for (var i=0; i<count; i++) {
        f(i);
    }
}

function g(x, y) {
    console.log("called g(" + x + ", " + y + ")");
}

callf(function(x) { g(x, "hey!"); }, 10);
6502
  • 112,025
  • 15
  • 165
  • 265
0

Have the first call to myFunc return another function that accepts a single parameter. Then, when you do your initial call hof(val, myFunc(arg1, arg2, arg3));, that function is returned, and it only needs one more argument, val to finish executing, which is provided by hof.

function hof(val, func){
  func(val);
}

function myFunc(arg1, arg2, arg3){
  return function(val){
    // do something with val, arg1, arg2, arg3 here
  }
}

hof(val, myFunc(arg1, arg2, arg3));

Simple

If what you said is true, you could use the following code, but i don't see a point since you can run myFunc directly.

function myFunc(val, arg1, arg2, arg3){
  // do something
}

function hof(val, myfunc, arg1, arg2, arg3){
  return myFunc(val, arg1, arg2, arg3);
}

Curry

function customCurry(func){
  var args = [];
  var result;

  return function(){
    var l = arguments.length;

    for(var i = 0; i < l; i++){
        if(args.length > 3) break;
        args.push(arguments[i]);
    }

    if(args.length > 3){
        return result = func(args[3], args[0], args[1], args[2]);
    }
  }
}

var preloaded = customCurry(myFunc)(arg1, arg2, arg3);

hof(val, myFunc);

function hof(val, func){
    func(val);
}
Community
  • 1
  • 1
THEtheChad
  • 2,372
  • 1
  • 16
  • 20
  • myFunc is already defined as taking 4 arguments and doing something (it doesn't return a function). Sorry if it wasn't clear from the question, I've edited it for clarification. – tldr Sep 09 '14 at 05:56
  • I need a better idea of what exactly you're trying to do because this implementation doesn't make any sense. – THEtheChad Sep 09 '14 at 06:02
0

ecma5 added Function.bind(), which also curries:

 myFunc2=myFunc.bind(this, val, arg1, arg2, arg3);

if you want a re-usable generic function to make bind() a little easier:

 function hof(f, args){ return f.bind.apply(f, [this].concat(args));}

which returns a new function, parameters pre-filled with the previously passed value(s). Passing more arguments will shift them to the right, and you can access all of them via the arguments keyword.

dandavis
  • 16,370
  • 5
  • 40
  • 36
0

Let's use an example.

Suppose you had a simple function to sum up numbers:

var sum = function() {
    return Array.prototype.slice.call(arguments).reduce(function(sum, value) {
        return sum + value;
    }, 0);
};

You can't simply call it by passing the arguments in and then passing that to another function because it will evaluate the function, so we need to create a wrapper around the function to shift the arguments across:

var sumWrapper = function() {
    var args = Array.prototype.slice.call(arguments);
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments).concat(args);
        return sum.apply(null, innerArgs);
    }
};

We can then create your higher order function (assume the last argument is the function in question):

var higherOrderSum = function() {
    return arguments[arguments.length - 1].apply(null, Array.prototype.slice.call(arguments, 0, arguments.length - 1));
};

Usage:

alert(higherOrderSum(3, 4, sumWrapper(1, 2, 3)));
sahbeewah
  • 2,690
  • 1
  • 12
  • 18