Passing additional parameters in higher-order functions

前端 未结 6 1409
刺人心
刺人心 2020-12-13 11:01

Consider this example:



        
相关标签:
6条回答
  • 2020-12-13 11:43

    you want to curry your function like so :-

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

    excludeFoos returns a function for filtering. Many functional languages auto curry functions for you so you can do partial application

    Note, it is easier to embrace something like Ramda for js which is built around these concepts and allows you to pipe collections / filters etc

    0 讨论(0)
  • 2020-12-13 11:44

    With ES6:

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

    another option would be to use bind(), but I find it difficult to read:

    const foos = samples.filter(exclude.bind(null, "foos"))
    
    0 讨论(0)
  • 2020-12-13 11:44

    Here's one for you:

    There are a couple of answers that talk about curry, and partial-application.

    And that's a great direction.

    But once you really get higher-order functions, you can make this stuff really clean and easy to work with.

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

    So what does that do?
    It lets you pass in a function, and gives you a function. Until you have passed in enough arguments to run the function, it's going to keep passing you another function that expects more arguments.

    What good is that?

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

    Now it's really easy to define something like your test.

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

    But wait! There's more!

    const filter = curry((predicate, array) => array.filter(predicate));
    
    const removeFoos = filter(notEqual("foo"));
    removeFoos(samples);
    removeFoos(items);
    removeFoos(otherStuff);
    

    Now I have a function that filters out foos and I can just pass it arrays whenever I feel like it.

    Last one for now:

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

    Instead of writing

    h(g(f(x)));
    

    Compose lets me write

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

    So now, let's try something wild...

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

    Library Code

    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 Code

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

    And we're done.
    Easy as pie.

    0 讨论(0)
  • 2020-12-13 11:55

    Naming things

    "If you have the name of a spirit, you have power over it." – Gerald Jay Sussman

    Can you think of a better name for your exclude function? I know I can. It's known as notEqual. Simply knowing it as its true name makes it much more versatile when it comes to problem solving. "exclude" makes sense in the context of filtering an array, but somehow it makes less sense if we wanted to use the exclude function elsewhere.

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

    Functional programming is all about making functions as reusable as possible, so as we move forward, let's stick with

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

    Function.prototype.bind

    Function.prototype.bind is used to bind values to function parameters. It's commonly used because it's been native since ECMAScript 5 – meaning you can accomplish your goal without adding any additional dependencies or making any changes to your existing code.

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

    Partial Application

    Partial application takes a function and some arguments and produces another function of smaller arity – arity is a fancy word for "the number of arguments a function takes"

    Now that you're familiar with Function.prototype.bind, you already know partial application. The only difference is bind forces you to provide the context of a binding. Contexts are a bother in most functional programs, so sometimes it's easier to have a function that lets us partially apply without concerning ourselves with context.

    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, while similar to partial application, is another way to approach your problem. Currying takes a function of multiple arguments and transforms it into a sequence of unary functions – functions that take one argument each.

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

    If you're having trouble seeing how this is different than partial application, note you won't see much of a difference until function arity is greater than two – See also: contrast currying with partial application.

    As you can see, readability is starting to suffer a little bit. Instead of currying on the fly, if notEqual is under our control, we could define it in curried form from the start

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

    You may not have even noticed it, but partial (above) is defined in curried style!

    Related: "What do multiple arrow functions mean in JavaScript?"

    Currying is a massively powerful concept and useful in a wide variety of ways. You might say it's overkill for solving this single, isolated problem, and you'd be right. You'll really only start to see the benefits of currying when it is widely used in a program or language as it has a systemic effect – and ultimately, it provides abstraction over function arity itself.

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

    Final Remarks

    There's a lot of options available to you and you might be wondering which is the "correct" one to choose. If you're looking for a silver bullet, you'll be sad to learn there isn't one. As with everything there are trade-offs.

    I find partial/procedural application to be an indispensable tool, and therefore I try to write all of my JavaScript functions in fully curried form. That way I avoid dropping calls to partial and curry all over my program. The consequence of this is the code ends up looking a little foreign, at first – comparison functor • round-robin • make anything you want • higher-order generators and DIY iterators • id generator • generic function repetition • merge/flatten array • custom iteration

    Not all parts of your programs are fully under your control tho, right? Of course you're probably using some external dependencies and it's unlikely that they're going to have the perfect functional interface you're looking for. In such a case, you'll end up using partial and curry to interface with other code that you cannot change.

    Lastly, look at some of the functional libraries out there like folktalke or Ramda. I don't recommend either for beginner functional programmers, but something worth looking into after you cut your teeth.

    0 讨论(0)
  • 2020-12-13 12:05

    You can use bind() to create a new function with the bound params;

    //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

    0 讨论(0)
  • 2020-12-13 12:05

    Here's another version with a primitive curry function:

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

    0 讨论(0)
提交回复
热议问题