Ramda chain usage

后端 未结 4 508
深忆病人
深忆病人 2020-12-16 02:19

From the documentation:

var duplicate = n => [n, n];
R.chain(duplicate, [1, 2, 3]); //=> [1, 1, 2, 2, 3, 3]
R.chain(R.append, R.head)([1, 2, 3]); //=&g         


        
相关标签:
4条回答
  • 2020-12-16 02:42

    It is probably easier to first look at the abstract version of the R.chain for functions and to distinguish between functions m: r -> a treated as monads and functions f: a -> r -> b treated as Kleisli arrows, as mentioned in this answer.

    Then R.chain is defined as:

    // (a -> r -> b, r -> a) -> r -> b
    R.chain = (f, m) => x => f(m(x))(x)
    

    This can be useful, when x is some kind of configuration parameter, the same for both f and m. Then a = m(x) is the value returned by m for that parameter, and g = f(_)(x) is the function returned by f for the same parameter. Think of x as some kind of environment that goes into both m and f. Then the above definition can be broken down as:

    R.chain = (f, m) => x => {
        const a = m(x)
            , g = a => f(a)(x)
        return g(a)
    }
    

    In comparison, the R.map for functions corresponds to the case when f is independent of that parameter x:

    // (a -> b, r -> a) -> r -> b
    R.map = (f, m) => x => f(m(x))
    

    which, of course, is the usual function composition from the outside.

    Another conceptual approach to define chain (aka bind in Haskell) is to apply map (aka fmap) followed by flatten (aka join).

    R.chain = (f, m) => {
        // m1: r -> r -> b
        const m1 = R.map(f, m)
        // flattening to fm1: r -> b
        const fm1 = x => m1(x)(x)
        return fm1
    }
    

    Now for m = x => R.head(x) and f = a => x => R.append(a)(x), R.chain(f, m) is equivalent to putting the parameter x into both f and m and composing the results:

    x => R.append(R.head(x))(x)
    

    which gives the expected result.

    Warning. Note that the R.append function here must be curried as it represents the Kleisli arrow a -> r -> b. Incidentally, Ramda provides the same named function also as uncurried, but it is the curried one used here. To see this, let us get our custom uncurried R.append:

    const appendCustom = (a, b) => R.append(a, b)
    

    Then note how Ramda's REPL throws an error and gives unexpected result:

    // Define our own uncurried append
    const appendCustom = (a, b) => R.append(a, b)
    R.chain(appendCustom, R.head)([1, 2]);
    // => t(...) is not a function
    

    http://ramdajs.com/repl/?v=0.25.0#?%2F%2F%20Define%20our%20own%20uncurried%20append%0Aconst%20appendCustom%20%3D%20%28a%2C%20b%29%20%3D%3E%20R.append%28a%2C%20b%29%0AR.chain%28appendCustom%2C%20R.head%29%28%5B1%2C%202%5D%29%3B%0A

    What really happens here, appendCustom is executed in its curried form: appendCustom(a)(b), with the second call is delegated to some internal function returning the error.

    0 讨论(0)
  • 2020-12-16 02:51

    The second example is showing how R.chain can be used things other than arrays, such as functions (or anything implementing the Fantasy Land chain spec).

    If the concept of mapping and then concatenating an array is familiar to you, you can think of mapping a function over another function as plain function composition. The concatenating part will require further explanation.

    R.chain declares its signature as:

    Chain m => (a → m b) → m a → m b
    

    For arrays, we can swap the m with [] to get:

    (a → [b]) → [a] → [b]
    

    For functions that receive some argument r, it becomes:

    (a → r → b) → (r → a) → (r → b)
    

    So with only the knowledge of those types available, the only way to produce the final r → b function is to do the following:

    • Pass the received argument r to the second function to produce an a
    • Apply both the new a and the original r to the first function to produce the resulting b

    Or in code:

    // specialised to functions
    const chain = (firstFn, secondFn) =>
      x => firstFn(secondFn(x), x)
    

    Swapping in the functions from the example, you can see it becomes:

    x => R.append(R.head(x), x)
    

    If you are familiar with R.converge then this is effectively:

    R.converge(firstFn, [secondFn, R.identity])
    
    0 讨论(0)
  • 2020-12-16 02:54

    Hope this helps

    let R = require('ramda')
    
    // using vanillajs
    let append = (arr1) => (arr2) => arr2.concat(arr1)
    let double = (arr1) => arr1.map( x => 2*x )
    
    let chain = (f, g) => arr => {
       let yarr = g(arr)
       return f(yarr)(arr)
    }
    
    console.log(chain(
       append,
       double
    )([10, 15, 20]))
    
    //using Ramda
    console.log(R.chain(append, double)([10, 15, 20]))
    
    0 讨论(0)
  • 2020-12-16 02:57

    chain is (approximately) defined as (for functions): (fn, monad) => x => fn(monad(x))(x)

    Therefore, we can transform R.chain(R.append, R.head)([1, 2, 3]); like so:

    R.chain(R.append, R.head)([1, 2, 3]);
    
    R.append(R.head([1, 2, 3]), [1, 2, 3]); // This is the important step
    
    R.append(1, [1, 2, 3]);
    
    [1, 2, 3, 1];
    

    The actual source:

    var chain = _curry2(_dispatchable(['fantasy-land/chain', 'chain'], _xchain, function chain(fn, monad) {
      if (typeof monad === 'function') {
        return function(x) { return fn(monad(x))(x); };
      }
      return _makeFlat(false)(map(fn, monad));
    }));
    

    The important part of that is this:

    function chain(fn, monad) {
      if (typeof monad === 'function') {
        return function(x) { return fn(monad(x))(x); };
      }
      return _makeFlat(false)(map(fn, monad));
    }
    

    According to the comment in the code, "_makeFlat is a helper function that returns a one-level or fully recursive function based on the flag passed in."

    _makeFlat(false) seems to be equivalent to unnest and _makeFlat(true) seems to be equivalent to flatten.

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