私はこの演習を行ったばかりで、誰かに役立つことを期待して、どのように答えにたどり着いたかを共有したいと思います (基本的には質問の内容と同じで、文字だけが異なります)。
背景として、何を何をするかから始めましょfoldLeftうfoldRight。たとえば、リスト [1, 2, 3] に対する foldLeft の結果は、操作*と開始値zが値です。
((z * 1) * 2) * 3
foldLeft は、リストの値を左から右に段階的に消費するものと考えることができます。言い換えると、最初に値z(リストが空の場合の結果) から開始し、リストfoldLeftが 1 で始まりz * 1、値が、そして最後に 3 を作用させた後、値 になります。foldLeft2(z * 1) * 2((z * 1) * 2) * 3
1 2 3
Initially: z
After consuming 1: (z * 1)
After consuming 2: ((z * 1) * 2
After consuming 3: (((z * 1) * 2) * 3
この最終的な値は、(演習で求められるように)foldRight代わりに使用することを除いて、達成したい値です。foldLeftここで、リストの値を左から右に消費するのと同様に、リストの値を右から左に消費することに注意してくださいfoldRight。したがって、リスト [1, 2, 3] では、
- この foldRight は 3 と [何か] に作用し、[結果] を返します
- 次に、2 と [result] に作用し、[result2] を与えます。
- 最後に、それは 1 と [result2] に作用し、最終的な式を与えます
- 最終的な表現を
(((z * 1) * 2) * 3
言い換えるfoldRightと、 を使用すると、最初にリストが空の場合の結果、次にリストに [3] しか含まれていない場合の結果、次にリストが [2, 3] の場合の結果、そして最後に結果に到達します。リストは [1, 2, 3] です。
つまり、これらは を使用して到達したい値ですfoldRight:
1 2 3
Initially: z
After consuming 3: z * 3
After consuming 2: (z * 2) * 3
After consuming 1: ((z * 1) * 2) * 3
したがって、 からzまで(z * 3)に移動(z * 2) * 3する必要があり((z * 1) * 2) * 3ます。
値として、これを行うことはできません。任意の操作に対して、値(z * 3)から値に移動する自然な方法はありません。(交換可能で結合的であるため、乗算用がありますが、任意の操作を表すためにのみ使用しています。)(z * 2) * 3**
しかし、関数としてこれを行うことができるかもしれません! 「プレースホルダー」または「穴」を備えた関数が必要ですz。これは、それを適切な場所に配置するものです。
- たとえば、最初のステップの後 (3 に作用した後) には、プレースホルダー関数があります
z => (z * 3)。というか、関数は任意の値を取る必要がありz、特定の値に使用してきたので、これを と書きましょうt => (t * 3)。(この関数を入力に適用するとz、値が得られます(z * 3)。)
- 2 番目のステップの後 (2 と結果に作用した後)、おそらくプレースホルダー関数があり
t => (t * 2) * 3ますか?
最初のプレースホルダー関数から次のプレースホルダー関数に移動できますか? させて
f1(t) = t * 3
and f2(t) = (t * 2) * 3
f2に関しては何f1ですか?
f2(t) = f1(t * 2)
はい、できます!したがって、必要な関数は と を取り2、f1を与えf2ます。これを と呼びましょうg。私たちはg(2, f1) = f2どこにいるf2(t) = f1(t * 2)のか、言い換えれば
g(2, f1) =
t => f1(t * 2)
先に進めた場合にこれが機能するかどうか見てみましょう: 次のステップはRHS がorg(1, f2) = (t => f2(t * 1))と同じです。t => f1((t * 1) * 2))t => (((t * 1) * 2) * 3)
うまくいくようです!最後にz、この結果に適用します。
最初のステップは何ですか?上記で定義されているように、またの定義からも におよびを適用gします。したがって、アイデンティティ関数である必要があるようです。3f0f1f1(t) = t * 3f1(t) = f0(t * 3)gf0
新たに始めましょう。
Our foldLeft(List(1, 2, 3), z)(*) is ((z * 1) * 2) * 3
Types here: List(1, 2, 3) is type List[A]
z is of type B
* is of type (B, A) -> B
Result is of type B
We want to express that in terms of foldRight
As above:
f0 = identity. f0(t) = t.
f1 = g(3, f0). So f1(t) = f0(t * 3) = t * 3
f2 = g(2, f1). So f2(t) = f1(t * 2) = (t * 2) * 3
f3 = g(1, f2). So f3(t) = f2(t * 1) = ((t * 1) * 2) * 3
最後に、z に f3 を適用して、必要な式を取得します。すべてがうまくいきます。そう
f3 = g(1, g(2, g(3, f0)))
つまり、f3 =foldRight(xs, f0)(g)
g今回はx * y、任意の関数を使用する代わりに、 を定義しましょうs(x, y)。
これらすべてをまとめる
def foldLeft[A, B](xs: List[A], z: B)(s: (B, A) => B): B = {
val f0 = (b: B) => b
def g(a: A, f: B=>B): B=>B =
t => f(s(t, a))
foldRight(xs, f0)(g)(z)
}
この本を読んでいるこのレベルでは、より明確で理解しやすいので、私は実際にはこの形式を好みます。しかし、ソリューションの形式に近づくために、f0andの定義をインライン化できます (これが入力であり、コンパイラーがそれを推論するためg、 の型を宣言する必要はもうありません)。gfoldRight
def foldLeft[A, B](xs: List[A], z: B)(s: (B, A) => B): B =
foldRight(xs, (b: B) => b)((a, f) => t => f(s(t, a)))(z)
これはまさに問題の内容であり、記号が異なるだけです。foldRight についても、foldLeft に関して同様です。