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 つの質問があります。
- Fantasy-landの
Apply
仕様 (と呼びますApply
) とApply#ap
は
- Ramdaの
R.ap
実装と仕様Array
との関係Apply
- カリー化が果たす役割
R.lift
Apply
仕様を理解する
ファンタジーの世界Apply
では、ap
メソッドが定義されている場合、オブジェクトは仕様を実装します (そのオブジェクトもメソッドFunctor
を定義することによって仕様を実装する必要がありmap
ます)。
ap
メソッドには次のシグネチャがあります。
ap :: Apply f => f a ~> f (a -> b) -> f b
ファンタジーランドの型シグネチャ表記法では:
=>
は型制約を宣言しているためf
、上記の署名では type を参照していますApply
~>
はメソッド宣言を宣言します。そのため、私たちが参照する値をラップ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
vs. R.ap
) とパラメーターの順序です。
[a → b] → [a] → [b]
: これは に置き換えた場合のバージョンです。前のセクションで値と関数を同じコンテナーにラップする必要があることを覚えていますかApply f
? Array
そのため、s で使用R.ap
する場合Array
、最初の引数は関数のリストであり、1 つの関数のみを適用する場合でも、配列に入れます。
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
の配列の例では、アリティが 3 の関数が:にR.lift
渡されますが、3 つの配列でどのように機能しますか? また、カレーではありませんのでご注意ください。R.lift
var madd3 = R.lift((a, b, c) => a + b + c);
[1, 2, 3], [1, 2, 3], [1]
実際には(デリゲート先の)のソース コード内で、渡された関数はR.liftN
自動カリー化されており、値 (この場合は 3 つの配列) を反復処理して結果を返します: 各反復で、カリー化された関数で呼び出し、 1 つの値 (この場合は 1 つの配列)。言葉で説明するのは難しいので、コードで同等のものを見てみましょう。R.lift
ap
const R = require('ramda');
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
が理解できれば、例が明確になります。
R.lift
onを使用した別の例を次に示しApply
ます。
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 }
コメントで Scott Sauyet によって提案されたより良い例 (彼はかなりの洞察を提供しています。読むことをお勧めします) は理解しやすいでしょう。少なくとも、 s のR.lift
デカルト積を計算する方向を読者に示しArray
ます。
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]
お役に立てれば。