在高阶函数中传递其他参数

时间:2017-01-30 23:07:20

标签: javascript functional-programming ecmascript-6

考虑这个例子:



const samples = ["foo", "bar"];

const excludeFoos = function(item) {
  return item !== "foo";
}

const foos = samples.filter(excludeFoos);




如何在excludeFoos中传递附加参数?

例如:



const samples = ["foo", "bar"];

const exclude = function(item, str) {
  return item !== str;
}

// obviously won't work but you get the point
const foos = samples.filter(exclude("foo"));
console.log(foos); // ["bar"]




6 个答案:

答案 0 :(得分:26)

命名

  

“如果你有一个精神的名字,你就有权力。” - Gerald Jay Sussman

您能想到exclude功能的更好名称吗?我知道我可以。它被称为notEqual。简单地将其视为真正的名称使其在解决问题方面更具通用性。 “排除”在过滤数组的上下文中是有意义的,但如果我们想在其他地方使用exclude函数,它就会变得不那么有意义。

if (exclude(a,b))
  console.log("a and b are not equal")

功能编程就是让函数尽可能重用,所以当我们继续前进时,让我们坚持使用

const notEqual = (x,y) => x !== y

<强> Function.prototype.bind

Function.prototype.bind用于绑定值到函数参数。它是常用的,因为它自ECMAScript 5以来就是原生的 - 这意味着你可以在不添加任何其他依赖项或对现有代码进行任何更改的情况下实现目标。

const notEqual = (x,y) => x !== y

const samples = ['foo', 'bar']

const foos = samples.filter(notEqual.bind(null, 'foo'))

console.log(foos) // ["bar"]

部分申请

Partial application接受一个函数和一些参数并产生另一个较小的arity函数 - arity 是一个奇特的词,表示“函数所需的参数数量”

现在您已熟悉Function.prototype.bind,您已经知道部分申请。唯一的区别是bind会强制您提供绑定的上下文。在大多数功能程序中,上下文都很麻烦,所以有时候我们可以更容易地使用一个函数来让我们部分应用而不用考虑自己的上下文。

const partial = (f, ...xs) => (...ys) => f(...xs, ...ys)

const notEqual = (x,y) => x !== y

const samples = ['foo', 'bar']

const foos = samples.filter(partial(notEqual, 'foo'))

console.log(foos) // ["bar"]

<强烈>柯里

Currying,与部分应用类似,是解决问题的另一种方法。 Currying采用多个参数的函数,并将其转换为一系列一元函数 - 每个函数需要一个参数。

const notEqual = (x,y) => x !== y

const curry = f => x => y => f(x,y)

const samples = ['foo', 'bar']

const foos = samples.filter(curry(notEqual)('foo'))

console.log(foos) // ["bar"]

如果您在查看与部分应用程序有何不同之处时遇到问题,请注意,在函数arity大于2之前,您不会发现太多差异 - 另请参阅:contrast currying with partial application

正如您所看到的,可读性开始受到一点影响。如果notEqual在我们的控制之下,我们可以从头开始以咖喱形式定义

,而不是动态地进行干扰。

const notEqual = x => y => x !== y

const samples = ['foo', 'bar']

const foos = samples.filter(notEqual('foo'))

console.log(foos) // ["bar"]

您可能没有注意到它,但partial(上图)是以咖喱风格定义的!

  

相关: "What do multiple arrow functions mean in JavaScript?"

Currying是一个非常强大的概念,并且以各种方式有用。你可能会说解决这个单一的,孤立的问题是过度的,你是对的。当它被广泛用于程序或语言时,你才真正开始看到currying的好处,因为它有一个systemic effect - 最终,它提供了对函数arity本身的抽象。

const apply = f => x => f (x)

const notEqual = x => y => x !== y

const filter = f => xs => xs.filter(apply(f))

const notFoo = filter(notEqual('foo'))

const samples = ['foo', 'bar']

console.log(notFoo(samples)); // ["bar"]

最后备注

您可以使用很多选项,您可能想知道哪个选项是“正确的”。如果您正在寻找灵丹妙药,你会伤心的学习没有一个。和所有事情一样,需要权衡利弊。

我发现部分/程序应用程序是一个不可或缺的工具,因此我尝试以完全curry的形式编写所有JavaScript函数。这样我就可以避免在我的程序中放弃对partialcurry的调用。这样做的结果是代码最初看起来有些异国情调 - comparison functorround-robinmake anything you wanthigher-order generators and DIY iteratorsid generator•{{3 } {} generic function repetitionmerge/flatten array

并非你的程序的所有部分都完全在你的控制之下,对吗?当然,您可能正在使用一些外部依赖项,并且它们不太可能具有您正在寻找的完美功能接口​​。在这种情况下,您最终会使用partialcurry与您无法更改的其他代码进行交互。

最后,查看一些功能库,如custom iterationfolktalke。我不建议初学者的功能性程序员,但是在你切牙后值得研究。

答案 1 :(得分:6)

您可以使用bind()创建带有绑定参数的新函数;

//you can replace the param with anything you like, null is for the context
var excludeFoos = exclude.bind(null,"foos")
const foos = samples.filter(excludeFoos);

Live example here

答案 2 :(得分:4)

使用ES6:

const foos = samples.filter(x => exclude(x, "foos"));

另一种选择是使用bind(),但我发现很难阅读:

const foos = samples.filter(exclude.bind(null, "foos"))

答案 3 :(得分:3)

你想要像这样理解你的功能: -

const samples = ["foo", "bar"];

const exclude = function(s) {
  return item => item !== s;
}

const foos = samples.filter(exclude("foo"));
console.log(foos)

excludeFoos返回一个过滤函数。许多函数语言为您自动调整函数,以便您可以进行部分应用

注意,更容易接受类似Ramda for js的东西,这是围绕这些概念构建的,并允许您管道集合/过滤器等

答案 4 :(得分:2)

这是给你的一个:

有几个答案谈论咖喱和部分应用。

这是一个很好的方向。

但是一旦你真正获得更高阶的功能,你就可以使这些东西真正干净,易于使用。

const curry = (f, ...initialArgs) => (...extraArgs) => {
  const args = [...initialArgs, ...extraArgs];
  return args.length >= f.length ? f(...args) : curry(f, ...args);
};

那是做什么的呢? 它允许您传入一个函数,并为您提供一个函数。在你传入足够的参数来运行函数之前,它会继续向你传递另一个需要更多参数的函数。

这有什么用?

const multiply = curry((x, y) => x * y);
const double = multiply(2);
const triple = multiply(3);

double(2); // 4
triple(9); // 27

现在很容易定义像测试一样的东西。

const notEqual = curry((test, x) => test !== x);

// you could do it like this, to reuse `notFoo`
const notFoo = notEqual("foo");
samples.filter(notFoo);

// you could do it like this, if you don't need `notFoo`
samples.filter(notEqual("foo"));

但是等等!还有更多!

const filter = curry((predicate, array) => array.filter(predicate));

const removeFoos = filter(notEqual("foo"));
removeFoos(samples);
removeFoos(items);
removeFoos(otherStuff);

现在我有一个过滤掉foos的函数,只要我愿意,我就可以传递它。

现在最后一个:

const compose = (...fs) => x => fs.reduceRight((x, f) => f(x), x);

而不是写

h(g(f(x)));

撰写让我写

const hgf = compose(h, g, f);
hgf(x);
hgf(y);
hgf(z);

// it's read from right to left
const tto = compose(three, two, one);

// or from bottom to top
const tsf = compose(
  third,
  second,
  first
);

// because it runs like
y = third(second(first(x)));

现在,让我们尝试一些疯狂的事情......

// lib functions (Ramda would work fine)
const map = curry((transform, array) => array.map(transform));
const reduce = curry((summarize, seed, array) => 
  array.reduce(summarize, seed));
const flatMap = curry((transform, array) =>
  array.map(transform).reduce((a, b) => a.concat(b), []));

// business functions
const castToEmployee = personData => new Employee(personData);
const isWorking = ({ active }) => active;
const removeSuperiors = curry((user, employee) =>
  employee.role <= user.role);

const customEmployeeCriteria = (criteria, employee) => { /*...*/ };
const removeDuplicates = (arr, employee) =>
  arr.some(person => person.id === employee.id)
    ? arr
    : arr.concat(employee);

图书馆代码

const performCustomSearch = searchCriteria => 
  filter(cutomEmployeeCriteria(searchCriteria));

const getAuthorizedEmployeeList = currentUser =>
  filter(removeSuperiors(currentUser));

const buildEmployees = compose(
  filter(isWorking),
  map(castToEmployee),
);

const cleanResults = compose(
  filter(removeBrokenItem),
  map(removePrivateMembers),
  reduce(removeDuplicates, []),
);

const handleEmployeeRequest = (currentUser, searchCriteria) => compose(
  cleanResults,
  performCustomSearch(searchCriteria),
  getAuthorizedEmployeeList(currentUser),
  buildEmployees
);

API代码

//(maybe /employees/?search={...}&token=123)
router.get("/employees", (req, res) => {
  PersonService.getAll()
    .then(handleEmployeeRequest(req.user, req.query.search))
    .then(filteredEmployees => res.json(filteredEmployees));
});

我们已经完成了 很容易就是馅饼。

答案 5 :(得分:0)

这是另一个具有原始curry功能的版本:

&#13;
&#13;
const samples = ["foo", "bar"];

const exclude = function(item,str) {
  return item !== str;
}

function curry(func){
  return function(var1){
    return function(var2){
      return func(var1,var2); 
    };
  };
}

console.log(curry(exclude)('foo')('bar'));  // true
console.log(samples.filter(curry(exclude)('foo')));  // ["bar"]
&#13;
&#13;
&#13;