繁体   English   中英

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

[英]Passing additional parameters in higher-order functions

考虑这个例子:

 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"] 

命名的东西

“如果你有一个精神的名字,你就有权力。” - 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"] 

部分申请

部分应用程序接受一个函数和一些参数,并产生另一个较小的arity函数 - arity是一个奇特的单词,用于“函数所需的参数数量”

既然您熟悉Function.prototype.bind ,那么您已经知道了部分应用程序。 唯一的区别是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之前,您不会看到太多差异 - 另请参阅: 对比度currying与部分应用程序

如您所见,可读性开始受到一点影响。 如果notEqual在我们的控制之下,我们可以从一开始就以咖喱形式定义它,而不是在飞行中

 const notEqual = x => y => x !== y const samples = ['foo', 'bar'] const foos = samples.filter(notEqual('foo')) console.log(foos) // ["bar"] 

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

相关: “多个箭头函数在JavaScript中意味着什么?”

Currying是一个非常强大的概念,并且以各种方式有用。 你可能会说解决这个单一的,孤立的问题是过度的,你是对的。 当它被广泛用于程序或语言时,你才真正开始看到currying的好处,因为它具有系统效应 - 并且最终它提供了对函数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函数。 这样我就可以避免在整个程序中放弃partial调用和curry调用。 这样做的结果是代码最终看起来有点异国情调 - 首先是比较 仿函数循环做任何你想要的东西高阶生成器和DIY迭代器id生成器泛型函数重复合并/展平数组自定义迭代

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

最后,看一些功能库有像folktalkeRamda 我不推荐初学者功能程序员,但是在你切牙之后值得研究一下。

您可以使用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);

这里有实例

使用ES6:

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

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

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

你想像这样讨好你的功能: -

 const samples = ["foo", "bar"]; const exclude = function(s) { return item => item !== s; } const foos = samples.filter(exclude("foo")); console.log(foos) 

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

注意,更容易拥抱像rada这样的基于这些概念的js,并允许你管道集合/过滤器等

这是给你的一个:

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

这是一个很好的方向。

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

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

我们已经完成了。
非常简单。

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

 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"] 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM