Ramda.jsのソース、特に「lift」関数を見てください。
与えられた例は次のとおりです。
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]
したがって、結果の最初の数値は簡単です。a
、b
、およびc
は、すべて各配列の最初の要素です。 2つ目は、私が理解するのは簡単ではありません。引数は各配列の2番目の値(2、2、未定義)ですか、それとも最初の配列の2番目の値であり、2番目と3番目の配列の最初の値ですか?
ここで起こっていることの順序を無視しても、私は実際には価値がわかりません。最初にlift
を実行せずにこれを実行すると、配列concat
enatedが文字列になります。これはflatMap
のように機能しているように見えますが、その背後にあるロジックに従うことができないようです。
ベルギの答えは素晴らしいです。しかし、これについて考える別の方法は、もう少し具体的にすることです。リストは実際にはこれをキャプチャしていないため、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
について考える方法です。いくつかの値のレベルで機能する関数を受け取り、それらの値のコンテナーのレベルで機能する関数に持ち上げます。
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つの質問があります。
Apply
spec(これをApply
と呼びます)および_Apply#ap
_の機能R.ap
_ 実装と何が Array
はApply
仕様と関係がありますR.lift
_Apply
仕様を理解するファンタジーランドでは、オブジェクトはApply
メソッドが定義されている場合に ap
仕様を実装します(そのオブジェクトはFunctor
メソッドを定義することによって map
仕様も実装する必要があります)。
ap
メソッドには次の署名があります。
_ap :: Apply f => f a ~> f (a -> b) -> f b
_
=>
_は型制約を宣言しているため、上記の署名のf
は型Apply
を参照しています。~>
_はmethod宣言を宣言するため、ap
はApply
で宣言された関数であり、a
と呼ばれる値をラップします(以下の例で確認します。いくつかの ファンタジーランドの実装 のap
はこの署名と一致していませんが、考え方は同じです)2つのオブジェクトv
とu
(v = f a; u = f (a -> b)
)があるとしましょう。したがって、この式は有効ですv.ap(u)
、ここで注意すべき点がいくつかあります。
v
とu
はどちらもApply
を実装しています。 v
は値を保持し、u
は関数を保持しますが、それらはApply
と同じ「インターフェース」を持ちます(これは、_R.ap
_およびArray
に関して、以下の次のセクションを理解するのに役立ちます)a
と関数_a -> b
_はApply
を認識せず、関数は値a
を変換するだけです。コンテナ内に値と関数を配置するのはApply
であり、それらを抽出して値に対して関数を呼び出し、それらを元に戻すのはap
です。Ramda
の_R.ap
_を理解する_R.ap
_ の署名には2つのケースがあります。
Apply f => f (a → b) → f a → f b
:これは前のセクションの_Apply#ap
_の署名と非常に似ていますが、違いはap
の呼び出し方法(_Apply#ap
_と_R.ap
_)の順序です。 params。[a → b] → [a] → [b]
_:これは_Apply f
_をArray
に置き換えた場合のバージョンです。値と関数は、前のセクションで同じコンテナーにラップする必要があることを覚えていますか?そのため、_R.ap
_をArray
sとともに使用する場合、最初の引数は関数のリストです。1つの関数のみを適用する場合でも、それを配列に配置します。一例を見てみましょう。私はMaybe
from _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
_がArray
sのデカルト積を計算する方向を読者に示します。
_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]
_
お役に立てれば。
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);
}
}
_
ap
(f (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)
を使用しています。