web-dev-qa-db-ja.com

Ramda.jsの「リフト」に頭を包むことができません

Ramda.jsのソース、特に「lift」関数を見てください。

リフト

liftN

与えられた例は次のとおりです。

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

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

したがって、結果の最初の数値は簡単です。ab、およびcは、すべて各配列の最初の要素です。 2つ目は、私が理解するのは簡単ではありません。引数は各配列の2番目の値(2、2、未定義)ですか、それとも最初の配列の2番目の値であり、2番目と3番目の配列の最初の値ですか?

ここで起こっていることの順序を無視しても、私は実際には価値がわかりません。最初にliftを実行せずにこれを実行すると、配列concatenatedが文字列になります。これはflatMapのように機能しているように見えますが、その背後にあるロジックに従うことができないようです。

25
diplosaurus

ベルギの答えは素晴らしいです。しかし、これについて考える別の方法は、もう少し具体的にすることです。リストは実際にはこれをキャプチャしていないため、Ramdaは実際にはリスト以外の例をドキュメントに含める必要があります。

簡単な関数を見てみましょう:

_var add3 = (a, b, c) => a + b + c;
_

これは3つの数値で動作します。しかし、containersの保持番号がある場合はどうなりますか?おそらく、 Maybe sがあります。それらを単純に追加することはできません。

_const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!
_

(わかりました、これはJavascriptです。実際にはここでエラーをスローしません。すべきでないことを連結してみてください...しかし、それは間違いなくあなたが望むことをしません!)

その機能をコンテナのレベルまで引き上げることができれば、私たちの生活は楽になります。ベルギが_lift3_として指摘したことは、liftN(3, fn)と、提供された関数のアリティを単純に使用する光沢のあるlift(fn)を使用してRamdaに実装されています。だから、私たちはできる:

_const madd3 = R.lift(add3);
madd3(Just(10), Just(15), Just(17)); //=> Just(42)
madd3(Just(10), Nothing(), Just(17)); //=> Nothing()
_

しかし、この持ち上げられた関数は、コンテナに固有のことは何も知らず、apを実装しているだけです。 Ramdaは、リストの外積のタプルに関数を適用するのと同様の方法で、リストにapを実装するため、次のことも実行できます。

_madd3([100, 200], [30, 40], [5, 6, 7]);
//=> [135, 136, 137, 145, 146, 147, 235, 236, 237, 245, 246, 247]
_

それが私がliftについて考える方法です。いくつかの値のレベルで機能する関数を受け取り、それらの値のコンテナーのレベルで機能する関数に持ち上げます。

36
Scott Sauyet

Scott SauyetとBergiからの回答のおかげで、私は頭を包みました。そうすることで、すべてのピースをまとめるためにジャンプするフープがまだあると感じました。私は旅の中で私が持っていたいくつかの質問を文書化します、それがいくつかの助けになることを願っています。

これが私たちが理解しようとしている_R.lift_の例です:

_var 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]
_

私にとって、それを理解する前に答えられるべき3つの質問があります

  1. Fantasy-land 's Apply spec(これをApplyと呼びます)および_Apply#ap_の機能
  2. Ramdaの _R.ap_ 実装と何が ArrayApply仕様と関係があります
  3. カリー化はどのような役割を果たしますか _R.lift_

Apply仕様を理解する

ファンタジーランドでは、オブジェクトはApplyメソッドが定義されている場合に ap 仕様を実装します(そのオブジェクトはFunctorメソッドを定義することによって map 仕様も実装する必要があります)。

apメソッドには次の署名があります。

_ap :: Apply f => f a ~> f (a -> b) -> f b
_

ファンタジーランドの型署名表記

  • _=>_は型制約を宣言しているため、上記の署名のfは型Applyを参照しています。
  • _~>_はmethod宣言を宣言するため、apApplyで宣言された関数であり、aと呼ばれる値をラップします(以下の例で確認します。いくつかの ファンタジーランドの実装apはこの署名と一致していませんが、考え方は同じです)

2つのオブジェクトvuv = f a; u = f (a -> b))があるとしましょう。したがって、この式は有効ですv.ap(u)、ここで注意すべき点がいくつかあります。

  • vuはどちらもApplyを実装しています。 vは値を保持し、uは関数を保持しますが、それらはApplyと同じ「インターフェース」を持ちます(これは、_R.ap_およびArrayに関して、以下の次のセクションを理解するのに役立ちます)
  • aと関数_a -> b_はApplyを認識せず、関数は値aを変換するだけです。コンテナ内に値と関数を配置するのはApplyであり、それらを抽出して値に対して関数を呼び出し、それらを元に戻すのはapです。

Ramdaの_R.ap_を理解する

_R.ap_ の署名には2つのケースがあります。

  1. Apply f => f (a → b) → f a → f b:これは前のセクションの_Apply#ap_の署名と非常に似ていますが、違いはapの呼び出し方法(_Apply#ap_と_R.ap_)の順序です。 params。
  2. _[a → b] → [a] → [b]_:これは_Apply f_をArrayに置き換えた場合のバージョンです。値と関数は、前のセクションで同じコンテナーにラップする必要があることを覚えていますか?そのため、_R.ap_をArraysとともに使用する場合、最初の引数は関数のリストです。1つの関数のみを適用する場合でも、それを配列に配置します。

一例を見てみましょう。私はMaybefrom _ramada-fantasy_ を使用しています。これはApplyを実装しますが、ここでの1つの矛盾は、 _Maybe#ap_ の署名が: ap :: Apply f => f (a -> b) ~> f a -> f b。他のいくつかの_fantasy-land_実装もこれに従っているようですが、それは私たちの理解に影響を与えるべきではありません:

_const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;

const a = Maybe.of(2);
const plus3 = Maybe.of(x => x + 3);
const b = plus3.ap(a);  // invoke Apply#ap
const b2 = R.ap(plus3, a);  // invoke R.ap

console.log(b);  // Just { value: 5 }
console.log(b2);  // Just { value: 5 }
_

_R.lift_の例を理解する

_R.lift_ の配列の例では、アリティが3の関数が_R.lift_に渡されます:var madd3 = R.lift((a, b, c) => a + b + c);、3つの配列でどのように機能するか_[1, 2, 3], [1, 2, 3], [1]_?また、カレーではありませんのでご注意ください。

実際には _R.liftN_ (_R.lift_が委任するソースコード)内で、渡される関数はauto-curriedであり、その後値(この場合は3つの配列)を反復処理し、結果を返します。各反復で、カレー関数と1つの値(この場合は1つの配列)を使用してapを呼び出します。言葉で説明するのは難しいです、コードで同等のものを見てみましょう:

_const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;

const madd3 = (x, y, z) => x + y + z;

// example from R.lift
const result = R.lift(madd3)([1, 2, 3], [1, 2, 3], [1]);

// this is equivalent of the calculation of 'result' above,
// R.liftN uses reduce, but the idea is the same
const result2 = R.ap(R.ap(R.ap([R.curry(madd3)], [1, 2, 3]), [1, 2, 3]), [1]);

console.log(result);  // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
console.log(result2);  // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
_

_result2_の計算式が理解されると、例が明らかになります。

Applyで_R.lift_を使用する別の例を次に示します。

_const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;

const madd3 = (x, y, z) => x + y + z;
const madd3Curried = Maybe.of(R.curry(madd3));
const a = Maybe.of(1);
const b = Maybe.of(2);
const c = Maybe.of(3);
const sumResult = madd3Curried.ap(a).ap(b).ap(c);  // invoke #ap on Apply
const sumResult2 = R.ap(R.ap(R.ap(madd3Curried, a), b), c);  // invoke R.ap
const sumResult3 = R.lift(madd3)(a, b, c);  // invoke R.lift, madd3 is auto-curried

console.log(sumResult);  // Just { value: 6 }
console.log(sumResult2);  // Just { value: 6 }
console.log(sumResult3);  // Just { value: 6 }
_

コメントでScottSauyetによって提案されたより良い例(彼はかなりの洞察を提供します、私はあなたがそれらを読むことをお勧めします)は理解しやすいでしょう、少なくともそれは_R.lift_がArraysのデカルト積を計算する方向を読者に示します。

_var madd3 = R.lift((a, b, c) => a + b + c);
madd3([100, 200], [30, 40, 50], [6, 7]); //=> [136, 137, 146, 147, 156, 157, 236, 237, 246, 247, 256, 257]
_

お役に立てれば。

15
Dapeng Li

lift/liftNは、通常の関数をアプリケーションコンテキストに「リフト」します。

_// lift1 :: (a -> b) -> f a -> f b
// lift1 :: (a -> b) -> [a] -> [b]
function lift1(fn) {
    return function(a_x) {
        return R.ap([fn], a_x);
    }
}
_

apf (a->b) -> f a -> f b)のタイプも理解しやすいものではありませんが、リストの例は理解できるはずです。

ここで興味深いのは、リストを渡してリストを取得することです。したがって、最初のリストの関数が正しいタイプである限り、これを繰り返し適用できます。

_// lift2 :: (a -> b -> c) -> f a -> f b -> f c
// lift2 :: (a -> b -> c) -> [a] -> [b] -> [c]
function lift2(fn) {
    return function(a_x, a_y) {
        return R.ap(R.ap([fn], a_x), a_y);
    }
}
_

また、例で暗黙的に使用した_lift3_も同じように機能しますが、現在はap(ap(ap([fn], a_x), a_y), a_z)を使用しています。

8
Bergi