7

以下を考えると:

var average = R.lift(R.divide)(R.sum, R.length)

これが の無意味な実装として機能するのはaverageなぜですか? R.sumなぜ渡すことができるのか、R.lengthいつそれらが関数なのかがわからないため、次の例とは異なり、リフトさR.divideれた関数をマップできません。R.sumR.length

var sum3 = R.curry(function(a, b, c) {return a + b + c;});
R.lift(sum3)(xs)(ys)(zs)

上記の場合、 、xsysおよびの値はzs、非決定論的なコンテキストで合計されます。この場合、持ち上げられた関数は、指定された計算コンテキストの値に適用されます。

R.apさらに詳しく説明すると、持ち上げられた関数を適用することは、各引数に連続して使用するようなものであることがわかりました。どちらの行も同じ出力に評価されます。

R.ap(R.ap(R.ap([tern], [1, 2, 3]), [2, 4, 6]), [3, 6, 8])
R.lift(tern)([1, 2, 3], [2, 4, 6], [3, 6, 8])

ドキュメントを確認すると、次のように書かれています。

アリティ > 1 の関数を「リフト」して、リスト、関数、または FantasyLand Apply 仕様を満たすその他のオブジェクトに「マップ」できるようにします。

そして、それは少なくとも私にとってはあまり有用な説明のようには思えません. の使用法に関する直感を構築しようとしていますlift。誰かがそれを提供できることを願っています。

4

2 に答える 2

13

最初のクールなことは、a -> bサポートできることmapです。はい、関数はファンクターです!

のタイプを考えてみましょうmap:

map :: Functor f => (b -> c) -> f b -> f c

Functor f => f具体的な型をArray与えるために置き換えましょう:

map :: (b -> c) -> Array b -> Array c

今回Functor f => fは次のように置き換えます。Maybe

map :: (b -> c) -> Maybe b -> Maybe c

相関関係は明らかです。バイナリ型をテストするためFunctor f => fに, に置き換えましょう:Either a

map :: (b -> c) -> Either a b -> Either a c

a関数の型をからbとして表現することがよくありますa -> bが、それは の単なる砂糖ですFunction a bEither長い形式を使用して、上記の署名をFunction次のように置き換えましょう。

map :: (b -> c) -> Function a b -> Function a c

b -> cしたがって、関数をマッピングすると、元の関数の戻り値に関数を適用する関数が得られます。a -> b砂糖を使用して署名を書き換えることができます。

map :: (b -> c) -> (a -> b) -> (a -> c)

何か気づきましたか?の型はcompose何ですか?

compose :: (b -> c) -> (a -> b) -> a -> c

関数型に特化したcomposeだけです!map

2 つ目のクールな点は、a -> bをサポートできることapです。関数もアプリカティブ ファンクターですこれらはFantasy Land 仕様ではApplyとして知られています。

のタイプを考えてみましょうap:

ap :: Apply f => f (b -> c) -> f b -> f c

Apply f => fに置き換えましょうArray

ap :: Array (b -> c) -> Array b -> Array c

今、とEither a

ap :: Either a (b -> c) -> Either a b -> Either a c

今、とFunction a

ap :: Function a (b -> c) -> Function a b -> Function a c

とはFunction a (b -> c)? 2 つのスタイルを混在させているので少し混乱しますが、type の値を取り、 from toaの関数を返す関数です。スタイルを使って書き直してみましょう:bca -> b

ap :: (a -> b -> c) -> (a -> b) -> (a -> c)

map「持ち上げる」ことがapできるあらゆるタイプ。見てみましょうlift2

lift2 :: Apply f => (b -> c -> d) -> f b -> f c -> f d

Function aは Apply の要件を満たしているので、次のように置き換えることApply f => fができFunction aます。

lift2 :: (b -> c -> d) -> Function a b -> Function a c -> Function a d

どちらがより明確に書かれています:

lift2 :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)

最初の式をもう一度見てみましょう。

//    average :: Number -> Number
const average = lift2(divide, sum, length);

何をしaverage([6, 7, 8])ますか?a( ) が関数 ( ) に[6, 7, 8]与えられ、( )が生成されます。も関数 ( ) に与えられ、( ) が生成されます。これで aと aができたので、それらを関数 ( ) に渡して、最終結果である( ) を生成できます。a -> bsumb21aa -> clengthc3bcb -> c -> ddivided7

したがって、Function 型はmapおよびをサポートできるため、( 、、およびを介して) 無料でap取得できます。必要ないので、実際にはRamdaから削除したいと思います。convergeliftlift2lift3converge


R.liftこの回答では意図的に使用を避けたことに注意してください。任意のアリティの関数をサポートするという決定により、意味のない型シグネチャと複雑な実装があります。一方、Sanctuary のアリティ固有のリフティング関数には、明確な型シグネチャと単純な実装があります。

于 2016-09-17T13:10:19.183 に答える
0

同じ問題を理解するのに苦労したので、Ramda のソース コードをのぞいてみることにしました。これについては、今後ブログ記事を書く予定です。liftその間に — ラムダの作業の手順をコメント付きでまとめました。

から: https://gist.github.com/philipyoungg/a0ab1efff1a9a4e486802a8fb0145d9e

// Let's make an example function that takes an object and return itself.
// 1. Ramda's lift level
lift(zipObj)(keys, values)({a: 1}) // returns {a: 1}

// this is how lift works in the background
module.exports = _curry2(function liftN(arity, fn) {
  var lifted = curryN(arity, fn);
  return curryN(arity, function() {
    return _reduce(ap, map(lifted, arguments[0]), Array.prototype.slice.call(arguments, 1)); // found it. let's convert no 1 to no 2
  });
});

// 2. Ramda's reduce level
reduce(ap, map(zipObj, keys))([values])
// first argument is the function, second argument is initial value, and the last one is lists of arguments. If you don't understand how reduce works, there's a plenty of resources on the internet

// 3. Ramda's ap level
ap(map(zipObj, keys), values)

// how ap works in the background
module.exports = _curry2(function ap(applicative, fn) {
  return (
    typeof applicative.ap === 'function' ?
      applicative.ap(fn) :
    typeof applicative === 'function' ? // 
      function(x) { return applicative(x)(fn(x)); } : // because the first argument is a function, ap return this.
    // else
      _reduce(function(acc, f) { return _concat(acc, map(f, fn)); }, [], applicative)
  );
});

// 4. Voilà. Here's the final result.
map(zipObj, keys)({a: 1})(values({a: 1}))

// Hope it helps you and everyone else!
于 2016-12-31T19:18:39.380 に答える