代码之家  ›  专栏  ›  技术社区  ›  kaiser

将相同的参数应用于Lodash流回调

  •  0
  • kaiser  · 技术社区  · 4 年前

    洛达斯 flow() 功能正常工作,如所示 this question ,通过在数组中给它一堆函数。上一个函数的结果将应用于下一个函数。

    这正是我的用例的问题所在,因为它抛弃了提供的任何论点。我需要将它们传递给行中的下一个函数,而不仅仅是返回值 *) .

    *) 是的,返回值应该经过修改后返回,并传递给下一个函数

    使用 flow() 这正是我想到的。任何其他实现目标的解决方案都是可以的。


    这是什么 :

    _.flow( [ firstFn, secondFn ] )
    ( first, second, third ) => firstFn( first, second, third ) => return first * first
    ( first ) => secondFn( first )
    

    这是什么 应该做 :

    _.flow( [ firstFn, secondFn ] )
    ( first, second, third ) => firstFn( first, second, third ) => return first * first
    ( first, second, third ) => secondFn( resultFromFirstFn, second, third )
    
    

    以下是一些演示代码:

    const checkLog = ( val, attr, group ) => {
      console.log( val, attr, group )
      return val
    }
    
    // This basically is "easy to use"-API and what will be mostly used.
    const Callbacks = {
        demo: [ _.trim, _.toLower, checkLog ],
    }
    
    // _.cond() is just a switch
    // [ <condition-to-exec-cb>, <cb-if-cond-true> ] 
    const cond = _.cond( _.map( 
      [
        [ _.stubTrue, Callbacks.demo ],
      ],
      // Monkey patching _.flow() to each array of cond/cb above
      cb => _.set( cb, '1', _.flow( _.last( cb ) ) ) 
    ) )
    
    const res = _.map( {
      " city": " Sample City",
      "street": "Sample Street",
    }, cond )
    
    console.log( res )
    

    注意:如果你想知道我为什么这样写:有一行注释指向哪个部分将被更改和扩展 很多 在未来,由于目标群体在那里工作,这必须很简单。所以它只是一组堆叠的回调函数。

    0 回复  |  直到 4 年前
        1
  •  2
  •   VLAZ Sohail Arif    4 年前

    问题在于 _.flow() 它期望第一个参数之后的所有内容都是一元函数,这样它就可以将结果传递给所有参数。当你想对所有函数应用相同的参数集,只更改第一个参数时,这是一个挑战。有几种方法可以做到这一点。

    使用Lodash

    你可以利用 _.partialRight 对每个功能进行部分分配。 partialRight 将根据函数接受的参数数量,从右到左应用参数。

    const fn = (a, b, c, d) => console.log(a, b, c, d);
    
    // behaves similar to (a, b) => console.log(a, b, "yak", "zebra")
    const newFn = _.partialRight(fn, "yak", "zebra");
    
    newFn("alpaca", "beaver");
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js"></script>

    但是,您仍然可以从左向右传递参数,这将把部分应用的参数向右推:

    const fn = (a, b, c, d) => console.log(a, b, c, d);
    
    // behaves similar to (a, b) => console.log(a, b, "yak", "zebra")
    const newFn = _.partialRight(fn, "yak", "zebra");
    
    newFn("alpaca", "beaver", "cat");
    <脚本src=“https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js“></script>

    假设所有功能都具有 最多 n 我们可以提出的论点数量:

    1. 获取数组 n 要应用于所有函数的参数。
    2. 获取所有功能。
    3. 部分权利 除了第一个参数外,所有函数都使用其他参数。自从我们申请 n-1 每个参数的参数,现在所有参数都可以被调用,就像它们是一元的一样。
    4. 使用 flow 关于step的新功能 3.
    5. 使用第一个参数启动链。

    然后,这将调用所有函数,以便 第一个论点 最后结果 第二个及以后的参数将基于初始参数,因此所有函数的参数都是相同的

    function nAryFlow(argsArray, ...fns) { //1. and 2. - get arguments and functions
      const start = argsArray[0];
      const rest = argsArray.slice(1);
    
      const convertedFns = fns.map(f => _.partialRight(f, ...rest)) //3. Turn all functions into unary ones
      return _.flow( //4. `flow` through the functions
        ...convertedFns
      )(start); //5. use the initial fist argument to kick things off
    }
    
    const fn1 = (who1, who2, who3) =>    `${who1}, ${who2}, and ${who3} are best friends.`;
    const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
    const fn3 = (story, who2) =>         `${story} They all met at a party thrown by ${who2}`;
    
    const args = ["Alice", "Bob", "Carol"];
    
    const result = nAryFlow(args, fn1, fn2, fn3);
    
    console.log(result)
    <脚本src=“https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js“></script>

    不使用Lodash

    以上所有操作都可以在没有Lodash的情况下轻松完成。相反,我们可以使用 Array#reduce 遍历所有函数并应用参数。这一次,我们直接应用它们,而不是预处理函数,但整体操作和效果是相同的:

    1. 第一个函数接受所有参数。
    2. 任何进一步的函数都接受最后一个函数和从开始的第二个参数的结果:

    function nAryFlow(argsArray, ...fns) {
      const start = argsArray[0];
      const rest = argsArray.slice(1);
      
      return fns.reduce((last, f) => f(last, ...rest), start);
    }
    
    const fn1 = (who1, who2, who3) =>    `${who1}, ${who2}, and ${who3} are best friends.`;
    const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
    const fn3 = (story, who2) =>         `${story} They all met at a party thrown by ${who2}`;
    
    const args = ["Alice", "Bob", "Carol"];
    
    const result = nAryFlow(args, fn1, fn2, fn3);
    
    console.log(result)

    使用更高阶函数的变体

    作为一种变体,可以将其拆分为多个高阶函数,这可能会在某些情况下产生更好的语法 nAryFlow(f1, f2, f3, fN)(arg1, arg2, arg3, argN) :

    function nAryFlow(...fns) {
      return function (...args) {
        const start = args[0];
        const rest = args.slice(1);
    
        return fns.reduce((last, f) => f(last, ...rest), start);
      }
    }
    const fn1 = (who1, who2, who3) =>    `${who1}, ${who2}, and ${who3} are best friends.`;
    const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
    const fn3 = (story, who2) =>         `${story} They all met at a party thrown by ${who2}`;
    
    const chain = nAryFlow(fn1, fn2, fn3);
    
    const result1 = chain("Alice", "Bob", "Carol");
    const result2 = chain("Rachel Green", "Monica Geller", "Chandler Bing");
    
    console.log(result1);
    console.log(result2);

    使用函数

    对于这里稍微不同的观点,你可以使用一个代数结构,称为 Functor 以简化语法。关于functor的本质是它们有一个 .map() 接受函数的方法。如果你想起 Array#map 那么你没有错。

    基本思想是,functor持有一个值,并允许通过函数对其进行修改 .map() 然后,它可以规定如何将函数应用于其值。结果 .map 始终与已映射的functor类型相同,因此您始终可以继续映射,并确保每次应用程序都是相同的。对于数组,你总是会得到一个长度相同的新数组,其中每个成员都被转换了。其他函数子可以应用给定的函数 .map() 与数组的作用不同,但它始终是一致的。

    那么,背景完成后,这个函数子看起来是这样的:

    class NAryFlow {
      constructor(argsArray) {
        this.value = argsArray[0];
        this.rest = argsArray.slice(1);
      }
      
      static of(argsArray) {
        return new NAryFlow(argsArray);
      }
    
      map(fn) {
        return NAryFlow.of(
          [ fn(this.value, ...this.rest), ...this.rest ]
        );
      }
    }
    
    const fn1 = (who1, who2, who3) =>    `${who1}, ${who2}, and ${who3} are best friends.`;
    const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
    const fn3 = (story, who2) =>         `${story} They all met at a party thrown by ${who2}`;
    
    const result = NAryFlow.of(["Alice", "Bob", "Carol"])
      .map(fn1)
      .map(fn2)
      .map(fn3)
      .value;
    
    console.log(result)

    与上述两个类似的想法-我们接受参数,并将它们全部应用于我们给出的每个函数 .map() 每次我们打电话 .map() 第一个论点将是最后一个结果。

    这里有一个细微的变化,使用 ES6 getters 我认为它的语法稍微好一些,但希望保持之前的实现更简单。

    class NAryFlow {
      constructor(argsArray) {
        this.args = argsArray;
      }
      
      static of(argsArray) {
        return new NAryFlow(argsArray);
      }
    
      map(fn) {
        return NAryFlow.of(
          [ fn(...this.args), ...this.rest ]
        );
      }
      
      get value() {
        return this.args[0];
      }
      
      get rest() {
        return this.args.slice(1);
      }
    }
    
    const fn1 = (who1, who2, who3) =>    `${who1}, ${who2}, and ${who3} are best friends.`;
    const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
    const fn3 = (story, who2) =>         `${story} They all met at a party thrown by ${who2}`;
    
    const result = NAryFlow.of(["Alice", "Bob", "Carol"])
      .map(fn1)
      .map(fn2)
      .map(fn3)
      .value;
    
    console.log(result)
        2
  •  0
  •   ibrahim mahrir    4 年前

    flow 不抛出任何参数,这只是javascript函数的工作方式,它们不尊重特定的签名。如果你调用一个只接受1个参数和100个参数的函数,它就不会抛出错误。

    阿尔索 被定义为将初始参数全部传递给第一个函数,而不管它是否会全部使用它们。

    您可以创建自己的 函数(因为它是一个 simple one )它只传递函数所需的确切参数量,并为其余函数保留其余参数。要知道一个函数需要多少个参数,你可以 check its length property 像这样:

    function flow(funcs) {
      const length = funcs.length;
      for(let func of funcs) {
        if (typeof func !== 'function') {
          throw new TypeError('Expected a function');
        }
      }
    
      return function(...args) {
        let result = args[0];
        for(let func of funcs) {
          result = func.apply(this, args.slice(0, func.length));  // call this function with only as many args as it needs
          args = [result, ...args.slice(func.length)];            // args becomes the result + the rest of args after the previous call
        }
        return result;
      }
    }
    

    注: 如果其中一个函数使用rest参数,在这种情况下,这将不起作用 长度 会回来的 0 。你不知道函数需要多少个参数。如果你想用所有可用参数调用该函数,那么只需更改 for 循环到:

    for(let func of funcs) {
      result = func.apply(this, args.slice(0, func.length || +Infinity));  // if 'func.length' is 0, '+Infinity' is used instead which will use all the available args
      args = [result, ...args.slice(func.length || +Infinity)];            // same goes here, '.slice(+Infinity)' will result in an empty array. 'result' will always be present which is the expected behavior
    }
    

    演示:

    function flow(funcs) {
      const length = funcs.length;
      for(let func of funcs) {
        if (typeof func !== 'function') {
          throw new TypeError('Expected a function');
        }
      }
    
      return function(...args) {
        let result = args[0];
        for(let func of funcs) {
          result = func.apply(this, args.slice(0, func.length));  // call this function with only as many args as it needs
          args = [result, ...args.slice(func.length)];            // args becomes the result + the rest of args after the previous call
        }
        return result;
      }
    }
    
    
    function square(n) {
      return n * n;
    }
    
    function add(a, b, c) {
      return a + b + c;
    }
    
    console.log(flow([square, add])(2, 3, 4));