【问题标题】:Can you give me an example of how to use Ramda lift?你能给我一个如何使用 Ramda 电梯的例子吗?
【发布时间】:2019-06-08 05:56:11
【问题描述】:

我正在阅读 ramda 文档

const madd3 = R.lift((a, b, c) => a + b + c);

madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]

它看起来是一个非常有用的功能。我看不出它有什么用例。

谢谢

【问题讨论】:

  • 嗯,我以为我们已经清理了那个文档。一个更清晰的例子是madd3([100, 200], [30, 40], [5, 6, 7]) //=> [135, 136, 137, 145, 146, 147, 235, 236, 237, 245, 246, 247]。给出的答案很棒,但另请参阅stackoverflow.com/q/36558598/1243641

标签: javascript functional-programming ramda.js


【解决方案1】:

这个函数只能接受数字:

const add3 = (a, b, c) => a + b + c;
add3(1, 2, 3); //=> 6

但是,如果这些数字都包含在函子中呢? (即包含值的事物;在下面的示例中为数组)

add3([1], [2], [3]); //=> "123"

这显然不是我们想要的。 您可以“提升”函数,以便它可以“提取”每个参数/函子的值:

const add3Lifted = lift(add3);
add3Lifted([1], [2], [3]); //=> [6]

数组显然可以保存多个值,并且与知道如何提取每个仿函数的值的提升函数结合使用,您现在可以这样做:

add3Lifted([1, 10], [2, 20], [3, 30]);
//=> [6, 33, 24, 51, 15, 42, 33, 60]

如果你这样做了,基本上你会得到什么:

[
  add3(1, 2, 3),    // 6
  add3(1, 2, 30),   // 33
  add3(1, 20, 3),   // 24
  add3(1, 20, 30),  // 51
  add3(10, 2, 3),   // 15
  add3(10, 2, 30),  // 42
  add3(10, 20, 3),  // 33
  add3(10, 20, 30)  // 60
]

请注意,每个数组的长度不必相同:

add3Lifted([1, 10], [2], [3]);
//=> [6, 15]

所以回答您的问题:如果您打算运行具有不同值集的函数,那么提升该函数可能是一个有用的考虑事项:

const results = [add3(1, 2, 3), add3(10, 2, 3)];

等同于:

const results = add3Lifted([1, 10], [2], [3]);

【讨论】:

    【解决方案2】:

    基本上它采用笛卡尔积并将函数应用于每个数组。

    const
        cartesian = (a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []),
        fn = ([a, b, c]) => a + b + c,
        result = [[1, 2, 3], [1, 2, 3], [1]]
            .reduce(cartesian)
            .map(fn);
    
    console.log(result); // [3, 4, 5, 4, 5, 6, 5, 6, 7]

    【讨论】:

      【解决方案3】:

      当前答案中没有提到的是,像 R.lift 这样的函数不仅适用于数组,而且适用于任何表现良好的 Apply1 数据类型。

      例如,我们可以复用R.lift产生的同一个函数:

      const lifted = lift((a, b, c) => a + b - c)
      

      将函数作为 Apply 类型:

      lifted(a => a * a,
             b => b + 5,
             c => c * 3)(4) //=> 13
      

      可选类型(派发到.ap):

      const Just = val => ({
        map: f    => Just(f(val)),
        ap: other => other.map(otherVal => val(otherVal)),
        getOr: _  => val
      })
      
      const Nothing = {
        map: f    => Nothing,
        ap: other => Nothing,
        getOr: x  => x
      }
      
      lifted(Just(4), Just(6), Just(8)).getOr(NaN) //=> 2
      
      lifted(Just(4), Nothing, Just(8)).getOr(NaN) //=> NaN
      

      异步类型(分派到.ap):

      const Asynchronous = fn => ({
        run: fn,
        map: f    => Asynchronous(g => fn(a => g(f(a)))),
        ap: other => Asynchronous(fb => fn(f => other.run(a => fb(f(a)))))
      })
      
      const delay = (n, x) => Asynchronous(then => void(setTimeout(then, n, x)))
      
      lifted(delay(2000, 4), delay(1000, 6), delay(500, 8)).run(console.log)
      

      ...等等。这里的要点是,任何可以维护任何 Apply 类型所期望的接口和法则的东西都可以使用泛型函数,例如 R.lift

      1。 Fantasy-land 规范中列出的 ap 的参数顺序与 Ramda 中名称调度支持的顺序相反,但在使用 fantasy-land/ap 命名空间方法时仍受支持。

      【讨论】:

        【解决方案4】:

        函数式编程是一个漫长的数学主题,尤其是the part dealing with monadscathegory theory in general。不过值得一看,here is a funny introduction with pictures

        简而言之,lift 是一个函数,它将采用 n-arguments 函数并生成一个采用 n wrapped-values 的函数并产生另一个结果 wrapped-value。采用单参数函数的电梯由以下类型签名定义

         // name :: f is a wrp-value => function -> wrp-value -> wrp-value
         liftA :: Applicative f   => (a -> b) -> f a -> f b
        

        等等...包装值?

        我将简要介绍一下 Haskell,只是为了解释这一点。在haskell 中,wrapped-value 的一个简单例子是Maybe,Maybe 可以是一个wrapped-value 或者什么都不是,这也是一个wrapped-value。下面的示例将一个函数应用于一个包含值的 Maybe 和一个空的 Maybe。

        > liftA (+ 8) (Just 8)
        Just 16
        > liftA (+ 8) Nothing
        Nothing
        

        列表也是一个包装值,我们可以对它应用函数。在第二种情况下,liftA2 将双参数函数应用于两个列表。

        > liftA (+ 8) [1,2,3]
        [9,10,11]
        > liftA2 (*) [1..3] [1..3]
        [1,2,3,2,4,6,3,6,9]
        

        这个 wrapped-value 是一个 Applicative Functor,所以从现在开始我将它称为 Applicative。

        也许也许你从这一点开始失去兴趣...... 但是我们之前的某个人已经迷失了这个话题,finally he survived and published it as an answer to this question

        让我们看看他看到了什么……

        ...

        他看到了幻想世界

        在幻想世界中,一个对象实现Apply规范,当它有 定义了 ap 方法(该对象也必须实现 Functor 规范通过定义 map 方法)。

        • Fantasy-land 是函数式编程规范的一个花哨名称 javascript。拉姆达紧随其后。
        • Apply 是我们的 Applicative,一个 Functor 也实现了 ap 方法。
        • Functor,是具有 ma​​p 方法的东西。

        所以,等等... javascript 中的数组有一个地图...

        [1,2,3].map((a)=>a+1) \\=> [ 2, 3, 4 ]
        

        那么 Array 是一个 Functor,map 对它的所有值应用一个函数,返回另一个具有相同数量值的 Functor。

        但是ap 有什么作用呢?

        ap 将函数列表应用于值列表。

        分派到第二个参数的 ap 方法,如果存在的话。还 将柯里化函数视为应用程序。

        让我们试着用它做点什么。

        const res = R.ap(
          [
            (a)=>(-1*a), 
            (a)=>((a>1?'greater than 1':'a low value'))
          ], 
          [1,2,3]); //=>  [ -1, -2, -3, "a low value", "greater than 1", "greater than 1" ]
        
        console.log(res);
        <script src="https://cdn.jsdelivr.net/npm/ramda@0.26.1/dist/ramda.min.js"></script>

        ap 方法接受一个函数数组(或其他一些 Applicative),并将其应用于值的 Applicative 以生成另一个扁平化的 Applicative。

        方法的签名说明了这一点

        [a → b] → [a] → [b]
        Apply f => f (a → b) → f a → f b
        

        最后,电梯有什么作用?

        Lift 接受一个带有 n 参数的函数,并产生另一个接受 n Aplicatives 并产生扁平化 Aplicative 的函数strong> 的结果。

        在这种情况下,我们的 Applicative 是数组。

        const add2 = (a, b) => a + b;
        const madd2 = R.lift(add2);
        
        const res = madd2([1,2,3], [2,3,4]); 
        //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
        
        console.log(res);
        // Equivalent to lift using ap
        const result2 = R.ap(R.ap(
          [R.curry(add2)], [1, 2, 3]),
          [2, 3, 4]
          );
        //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
        console.log(result2);
        <script src="https://cdn.jsdelivr.net/npm/ramda@0.26.1/dist/ramda.min.js"></script>

        这些包装器(Applicatives、Functors、Monads)很有趣,因为它们可以是实现这些方法的任何东西。在 haskell 中,这用于包装不安全的操作,例如输入/输出。它也可以是错误包装器或树,甚至是任何数据结构。

        【讨论】:

        • 一般来说,lift,或更准确地说是liftA2 等价于ap(map(x)),而不是ap(ap(x))。所以这是 Ramda 特定的并且偏离标准。
        • @reify: Ramda does implement liftN(因此lift)带有初始map,减少ap对结果的调用。
        猜你喜欢
        • 2012-12-29
        • 1970-01-01
        • 2010-11-10
        • 1970-01-01
        • 2021-09-09
        • 1970-01-01
        • 2020-09-20
        • 1970-01-01
        • 2015-02-11
        相关资源
        最近更新 更多