Javascript Macro: Implementing F # Direct String Operator

I want to implement a higher order function ( hof

) that essentially works like F #'s straight pipe operator (passes the 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 to call myFunc

(excluding the first argument, as it will val

)

But it 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 quite understand what the code is doing sweet.js

. It would be very helpful if you could comment out 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))
    }
}

      

+3


source to share


8 answers


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

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

      

or that:

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

      

The first one can be implemented as follows:



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

      

The second one can be implemented as follows:

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

      

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

myFunc(val, arg1, arg2, arg3);

      

+1


source


Isn't it called Currying ?

Anyway, here's an example of garbage, 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

      

+1


source


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

Straight tube in F #, the infix operator |>

applies the function on the right to the result of the expression on the left.

The new syntax should look like this ( |>

macro):

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

      

Tubes macro to add an argument to the function, so that after the expansion of the function will look like this: plus(seven, 3)

.

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

The main part is in this snippet return #{ /** macro expansion template */ }

. It contains javascript that is executed at compile time (sweet.js), returning text, which is the code to expand the macro. Compilation result:

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

      

length

functions in js is the number of positional arguments expected
.

A complex macro that allows you to use the 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 limitation / requirement of this macro is to omit parens ()

if the function in the pipeline takes no arguments.

The 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


source


You can easily use a closure for this type 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);

      

0


source


The first call to myFunc returns another function that takes one parameter. Then, when you make your initial call hof(val, myFunc(arg1, arg2, arg3));

, this function returns, and another argument is required to complete execution val

, which is provided 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));

      


Plain

If you told the truth, you can use the following code, but I don't see the 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);
}

      

0


source


ecma5 added Function.bind () which is also curry:

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

      

if you want a reusable generic function to simplify bind ():

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

      

which returns a new function, parameters pre-filled with previously passed values. Passing more arguments shifts them to the right, and you can access all of them using the arguments keyword.

0


source


Use an example.

Let's say you had a simple function to add numbers:

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

      

You can't just call it by passing arguments and then passing that to another function, because it will evaluate to the function, so we need to wrap around the function to shift the arguments through:

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));
};

      

Using:

alert(higherOrderSum(3, 4, sumWrapper(1, 2, 3)));

      

0


source


In ES6, this gets pretty compact:

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

      

0


source







All Articles