28

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

リフト

リフトN

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

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]

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

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

4

3 に答える 3

41

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

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

var add3 = (a, b, c) => a + b + c;

これは 3 つの数値で動作します。しかし、数字を保持するコンテナがあった場合はどうなるでしょうか? おそらく私たちはMaybesを持っています。それらを単純に追加することはできません。

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

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

その機能をコンテナのレベルまで持ち上げることができれば、私たちの生活は楽になるでしょう。Bergi が指摘したことは、提供された関数のアリティを単純に使用するおよびグロスを使用してlift3Ramda に実装されています。したがって、次のことができます。liftN(3, fn)lift(fn)

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

しかし、この持ち上げられた関数は、コンテナーに関する特定のことは何も認識しておらず、コンテナーが実装していることだけを認識していますap。Ramdaapは、関数をリストの外積のタプルに適用するのと同様の方法でリストを実装するため、次のようにすることもできます。

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

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

于 2016-04-12T15:39:42.810 に答える
19

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-landApply仕様 (と呼びますApply) とApply#ap
  2. RamdaのR.ap実装と仕様Arrayとの関係Apply
  3. カリー化が果たす役割R.lift

Apply仕様を理解する

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

apメソッドには次のシグネチャがあります。

ap :: Apply f => f a ~> f (a -> b) -> f b

ファンタジーランドの型シグネチャ表記法では:

  • =>は型制約を宣言しているためf、上記の署名では type を参照していますApply
  • ~>はメソッド宣言を宣言します。そのため、私たちが参照する値をラップapするように宣言された関数である必要があります(以下の例では、一部のファンタジーランドの実装がこの署名と一致していませんが、考え方は同じです)Applyaap

2 つのオブジェクトvu( 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 つのケースがあります。

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

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

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

実際には(デリゲート先の)のソース コード内で、渡された関数はR.liftN自動カリー化されており、値 (この場合は 3 つの配列) を反復処理して結果を返します: 各反復で、カリー化された関数で呼び出し、 1 つの値 (この場合は 1 つの配列)。言葉で説明するのは難しいので、コードで同等のものを見てみましょう。R.liftap

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.liftonを使用した別の例を次に示し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]

お役に立てれば。

于 2017-06-01T11:49:15.700 に答える
8

lift/liftN通常の関数を Applicative コンテキストに「持ち上げる」。

// 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).

于 2016-04-11T20:45:32.693 に答える