Передача дополнительных параметров в функции более высокого порядка

Рассмотрим следующий пример:

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

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

const foos = samples.filter(excludeFoos);

Ответ 1

Именование вещей

"Если у вас есть имя духа, у вас есть власть над ним". – Джеральд Джей Суссман

Можете ли вы придумать лучшее имя для вашей функции 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"]

Ответ 2

Вы можете использовать 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);

Здесь показан живой пример

Ответ 3

С ES6:

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

другой вариант - использовать bind(), но мне трудно читать:

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

Ответ 4

вы хотите выполнить свою функцию следующим образом: -

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

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

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

Ответ 5

Здесь один для вас:

Есть несколько ответов, которые говорят о карри и частичном приложении.

И это отличное направление.

Но как только вы действительно получите функции более высокого порядка, вы можете сделать этот материал действительно чистым и легким в работе.

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

Compose позволяет мне писать

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

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

И все готово.
Легко, как пирог.

Ответ 6

Здесь другая версия с примитивной функцией 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"]