3

我想实现一个高阶函数 ( hof),它基本上像 F# 风格的正向管道运算符一样工作(将一个值作为第一个参数传递给另一个函数,myFunc)。我能想到的唯一方法是:

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

args_array调用的参数数组在哪里myFunc(不包括第一个参数,因为那将是val

但这对我来说看起来不是很优雅。有一个更好的方法吗?

编辑:我在 github https://gist.github.com/aaronpowell/d5ffaf78666f2b8fb033上找到了这个。但我真的不明白sweet.js代码在做什么。如果您可以注释代码,这将非常有帮助,特别是:

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

8 回答 8

1

这不是叫柯里化吗?

无论如何,这是一个垃圾示例,如果您搜索,我相信会有更好的示例:

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
于 2014-09-09T06:00:32.577 回答
1

Sweet.js 宏很简单,它们乍一看很神秘。

F# 语言中的正向管道,中缀运算符|>将右侧的函数应用于左侧表达式的结果。

新语法应如下所示(|>宏):

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

管道宏必须将参数附加到函数,以便扩展后的函数看起来像这样plus(seven, 3)

问题中的 sweet.js 宏适用于最简单的情况。

主要部分在这个片段return #{ /** macro expansion template */ }中。它包含在编译时执行的 JavaScript(sweet.js),返回的文本是宏扩展的代码。编译结果:

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

js 中函数的length是其预期位置参数的数量

允许使用对象中包含的函数的复杂宏(点表示法):

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

()如果管道中的函数不接受参数,则此宏的一个限制/要求是省略括号。

var outcome = 5 |> plus(seven) |> obj.subObj.func;扩展为 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));

于 2014-09-11T19:03:10.713 回答
1

如果您想要 F# 管道运算符之类的东西,我认为您最好的选择是您在帖子中采用的这种方法:

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

或这个:

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

第一个可以这样实现:

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

第二个可以这样实现:

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

但这一切都留下了为什么你不会以正常方式调用函数的问题:

myFunc(val, arg1, arg2, arg3);
于 2014-09-09T05:41:33.540 回答
1

在 ES6 中,这变得非常紧凑:

var hof = (fn, ...params) => (...addtl) => fn(...parms, ...addtl);
于 2014-09-30T16:49:37.150 回答
0

让第一次调用 myFunc 返回另一个接受单个参数的函数。然后,当您执行初始调用hof(val, myFunc(arg1, arg2, arg3));时,将返回该函数,并且它只需要一个参数val即可完成执行,该参数由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));

简单的

如果你说的是真的,你可以使用下面的代码,但我看不出有什么意义,因为你可以直接运行 myFunc。

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

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

咖喱

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);
}
于 2014-09-09T05:47:20.567 回答
0

您可以轻松地使用闭包进行这种包装:

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);
于 2014-09-09T05:42:55.297 回答
0

让我们举个例子。

假设你有一个简单的函数来总结数字:

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

您不能简单地通过传入参数然后将其传递给另一个函数来调用它,因为它将评估函数,因此我们需要在函数周围创建一个包装器以将参数转移到:

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

然后我们可以创建您的高阶函数(假设最后一个参数是有问题的函数):

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

用法:

alert(higherOrderSum(3, 4, sumWrapper(1, 2, 3)));
于 2014-09-09T06:25:05.500 回答
0

ecma5 添加了Function.bind(),它也咖喱:

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

如果您想要一个可重用的通用函数来使bind()更容易一些:

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

它返回一个新函数,参数预先填充了先前传递的值。传递更多参数会将它们向右移动,您可以通过arguments关键字访问所有参数。

于 2014-09-09T06:23:24.700 回答