我如何像 Elm 那样在 JS 中传递函数?

How can I pipe functions in JS like Elm does?

我刚开始学习函数式编程,我很难弄清楚如何做到这一点(如果这值得麻烦的话)。我研究了柯里化,但不确定这是否是我需要走的方向??还是管道?

我想从一个值开始,然后通过不同的函数将其通过管道传输。 Underscore 有类似的 'chain' 方法。但是我不想使用原型来做到这一点。我意识到解决方案可能与我的目标语法不匹配。

Elm 具有 |> 语法(如下),非常好看

// what i'd like to do (or similar) in JS *without using prototype*
num = ("(123) 456-7890")
  .removeDashes()
  .removeParens()
  .removeSpaces()

// what elm does
"(123) 456-7890"
  |> removeDashes
  |> removeParens
  |> rem


// functions I wrote so far

removeDashes = function(str) {
  return str.replace(/-/g, '');
};

removeParens = function(str) {
  return str.replace(/\(|\)/g, '');
};

removeSpaces = function(str) {
  return str.replace(/\s/g, '');
};


// what i'm currently doing

num =
  removeDashes(
    removeParens(
      removeSpaces(
        "(123) 456-7890"")));

正如最后所说,研究使用原型。字符串原型允许您向所有字符串添加 class 级别的功能:

String.prototype.removeParens = function() {
    this = this.replace(/\(|\)/g, '');
}

这可以让你做这样的事情:

var myString = "(test)";

myString.removeParens();

一旦您将其他函数添加到 String 原型中,您就可以像这样简单地链接函数调用:

myString.removeDashes().removeParens().removeSpaces();

等等

最简单的方法是将它们真正添加到原型链中,但您可以使用对象来实现。这是一个简单的例子:

function MyString( str ){
    var value = str.toString();

    return {
        removeDashes: function() {
            value = value.replace(/-/g, '');
            return this;
        },
        removeParens: function() {
            value = value.replace(/\(|\)/g, '');
            return this;
        },
        removeSpaces: function() {
            value = value.replace(/\s/g, '');
            return this;
        },
        toString: function (){
            return value;
        },
        valueOf: function (){
            return value;
        }
    };
}

您可以稍后执行此操作:

var clean = (new MyString('This \(Is)\/ Dirty'))
    .removeDashes()
    .removeParens()
    .removeSpaces();

这样,您的 prototype 就会保持干净。要检索非对象值,只需 运行 toStrong() 方法,toValue() 或对该值执行任何操作(连接 1,除以它,任何操作!)。

正如其他人所说,将函数添加到 String 原型是一个有效且简短的解决方案。但是,如果您不想将它们添加到 String 原型中,或者如果您想在将来执行更复杂的功能,另一种选择是制作一个包装器来处理这个:

function SpecialString(str){

    this.str = str;

    this.removeDashes = function() {
      this.str=this.str.replace(/-/g, '');
      return this;
    };

    this.removeParens = function() {
      this.str=this.str.replace(/\(|\)/g, '');
      return this;
    };

    this.removeSpaces = function() {
      this.str=this.str.replace(/\s/g, '');
      return this;
    };

    return this;
}

num = new SpecialString("(123) 456-7890").removeDashes().removeParens().removeSpaces();

console.log(num) // 1234567890

这是我在 lodash 中找到的一个解决方案,它允许你混合你自己的函数,然后在链上使用它们:

...

removeSpaces = function(str) {
  return str.replace(/\s/g, '');
};

_.mixin({
  removeSpaces: removeSpaces,
  removeParens: removeParens,
  removeDashes: removeDashes
});

num = _.chain("(123) 456-7890")
  .removeSpaces()
  .removeParens()
  .removeDashes()
  .value()

有多种方法可以解决这个问题,您已经在下划线和 Elm 中提供了参考。

在 Elm 中,柯里化函数是等式的重要组成部分。由于每个函数都接收一个参数,因此您可以构建其中一些部分应用的链,等待您在管道中编织的参数。这同样适用于 Haskell、PureScript 和同类语言。

在 JavaScript 中重现 ipsis literis 需要一点糖分——您可以使用 sweet.js macro 来获得实现它的源转换。

没有糖,它可以有很多用途。也许探索的一种方法是使用生成器,向下传递已解析链的位,直到获得非函数值。

不是一个非常严肃的建议,但是一个可行的建议:

var update = pipe()(removeDashes >> removeParens >> removeSpaces);

update("(123) 456-7890"); //=> "1234567890"

这是基于 pipe 的实现:

var pipe = function() {
    var queue = [];
    var valueOf = Function.prototype.valueOf;
    Function.prototype.valueOf = function() {
        queue.push(this);
        return 1;
    };
    return function() {
        Function.prototype.valueOf = valueOf;
        return function(x) {
            for (var i = 0, val = x, len = queue.length; i < len; i++) {
                val = queue[i](val);
            }
            return val;
        }
    };
};

您可以在 slide 33 of my talk on functional composition in js 中查看更多内容。

如果您想在 JavaScript 中尝试使用函数式编程,我建议您使用像 Underscore、Lodash 或 Ramda 这样的库。它们都具有 compose/pipe 功能。大多数时候,您希望将它与这些库也提供的某种形式的部分应用程序结合起来。

无论如何,尝试自己实现它是一个很好的练习。 我会这样解决...

/* Asumes es5 or higher */

function pipe (firstFn /* ...restFns */) {
  var _ = null;
  var _slice = Array.prototype.slice;
  var restFns = _slice.call(arguments, 1) || [];


  return function exec_fns() {
    var args = _slice.call(arguments, 0, 1);

    return restFns.reduce(function(acc, fn) {
      return fn.call(_, acc);
    }, firstFn.apply(_, args));
  }
}

removeDashes = function(str) {
  return str.replace(/-/g, '');
};

removeParens = function(str) {
  return str.replace(/\(|\)/g, '');
};

removeSpaces = function(str) {
  return str.replace(/\s/g, '');
};


console.log(pipe(
  removeDashes,
  removeParens,
  removeSpaces
)("(123) 456-7890") == "1234567890")

Also Functional JavaScript 的 Fogus 是深入挖掘这种编程风格的好资源

一行就可以创建管道函数,可读性好:

const pipe = (...fns) => fns.reduce((v, f) => v.constructor === Function ? v() : f(v));

它会这样使用:

var numResult = pipe('(123) 456-7890', removeDashes, removeParens, removeSpaces);

var pipe = (...fns) => fns.reduce((v, f) => v.constructor === Function ? v() : f(v));


function removeDashes(str) {
  return str.replace(/-/g, '');
}

function removeParens(str) {
  return str.replace(/\(|\)/g, '');
}

function removeSpaces(str) {
  return str.replace(/\s/g, '');
}

console.log(
 'result:', pipe('(123) 456-7890', removeDashes, removeParens, removeSpaces)
);

注意:此功能需要支持传播运算符的平台...

为了以防万一,我为此创建了一个支持异步函数(Promises)的模块,它也适用于 older/legacy 不能使用 spread ...[= 的平台16=]

https://github.com/DiegoZoracKy/pipe-functions