web-dev-qa-db-ja.com

scalaでFoldRightを使用するFoldLeft

Scalaでの関数型プログラミング を実行しているときに、次の質問に遭遇しました。

FoldRightの観点からfoldLeftを正しくすることはできますか?逆はどうですか?

著者によって提供されたソリューションでは、次のような実装を提供しています。

def foldRightViaFoldLeft_1[A,B](l: List[A], z: B)(f: (A,B) => B): B = 
    foldLeft(l, (b:B) => b)((g,a) => b => g(f(a,b)))(z)

  def foldLeftViaFoldRight[A,B](l: List[A], z: B)(f: (B,A) => B): B = 
    foldRight(l, (b:B) => b)((a,g) => b => g(f(b,a)))(z)

誰かが私がこのソリューションをたどって、これが実際にどのようにfoldlをfoldrの観点から実装するのか、またその逆を理解するのを手伝ってくれるでしょうか?

ありがとう

36
sc_ray

見てみましょう

_def foldLeftViaFoldRight[A,B](l: List[A], z: B)(f: (B,A) => B): B = 
  foldRight(l, (b:B) => b)((a,g) => b => g(f(b,a)))(z)
_

(他の折り目も同様です)。秘訣は、右折り操作中に、型Bの最終値を作成しないことです。代わりに、BからBまでの関数を作成します。フォールドステップは、タイプ_a: A_の値と関数_g: B => B_を取り、新しい関数_(b => g(f(b,a))): B => B_を生成します。この関数は、gf(_, a)の合成として表すことができます。

_  l.foldRight(identity _)((a,g) => g compose (b => f(b,a)))(z);
_

プロセスは次のように見ることができます。aの各要素lについて、関数_B => B_である部分適用b => f(b,a)を使用します。次に、 compose これらすべての関数を、右端の要素(トラバーサルを開始する要素)に対応する関数が構成チェーンの左端にくるようにします。最後に、zに大きな合成関数を適用します。これにより、左端の要素(コンポジションチェーンの右端)から始まり、右端の要素で終わる一連の操作が行われます。

更新:例として、この定義が2要素リストでどのように機能するかを調べてみましょう。まず、関数を次のように書き直します。

_def foldLeftViaFoldRight[A,B](l: List[A], z: B)
                             (f: (B,A) => B): B =
{
  def h(a: A, g: B => B): (B => B) =
    g compose ((x: B) => f(x,a));
  l.foldRight(identity[B] _)(h _)(z);
}
_

それを渡したときに何が起こるかを計算してみましょうList(1,2)

_List(1,2).foldRight(identity[B] _)(h _)
  = // by the definition of the right fold
h(1, h(2, identity([B])))
  = // expand the inner `h`
h(1, identity[B] compose ((x: B) => f(x, 2)))
  =
h(1, ((x: B) => f(x, 2)))
  = // expand the other `h`
((x: B) => f(x, 2)) compose ((x: B) => f(x, 1))
  = // by the definition of function composition
(y: B) => f(f(y, 1), 2)
_

この関数をzに適用すると

_f(f(z, 1), 2)
_

要求に応じ。

33
Petr Pudlák

私はこの演習を行ったばかりで、誰かに役立つことを願って、どのようにして答えにたどり着いたかを共有したいと思います(基本的に質問の内容と同じですが、文字だけが異なります)。

背景として、foldLeftfoldRightが行うことから始めましょう。たとえば、リスト[1、2、3]のfoldLeftの結果は、演算_*_で開始値zは値_((z * 1) * 2) * 3_です

FoldLeftは、リストの値を左から右に段階的に消費するものと考えることができます。つまり、最初は値z(リストが空の場合の結果)から開始し、次にfoldLeftに、リストが1で始まり、値が_z * 1_になることを明らかにし、foldLeftが表示します。次のリストには_2_があり、値は_(z * 1) * 2_になり、最後に3に作用した後、値_((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と[結果]に作用し、[結果2]を与えます。
  • 最後に、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_に移動する必要があります。

valuesであるため、これを行うことはできません。任意の操作_(z * 3)_に対して、値_(z * 2) * 3_から値_*_に移動する自然な方法はありません。 (可換で結合法則であるため乗算がありますが、任意の演算を表すために_*_のみを使用しています。)

しかし、functionsとして、これを実行できる可能性があります。 「プレースホルダー」または「穴」を持つ関数が必要です。これは、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)
_

これを進めた場合にこれが機能するかどうかを見てみましょう。次のステップはg(1, f2) = (t => f2(t * 1))で、RHSはt => f1((t * 1) * 2))またはt => (((t * 1) * 2) * 3)と同じです。

うまくいくようです!そして最後に、この結果にzを適用します。

最初のステップはどうあるべきですか? _3_と_f0_にgを適用して_f1_を取得します。ここで、f1(t) = t * 3は上記で定義したとおりですが、gの定義からもf1(t) = f0(t * 3)を取得します。したがって、恒等関数として_f0_が必要なようです。


新たに始めましょう。

_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)を意味します

今度は_x * y_の代わりに任意の関数s(x, y)を使用してgを定義しましょう:

  • gへの最初の引数はタイプAです
  • gの2番目の引数は、これらのfのタイプであり、_B => B_です。
  • したがって、gのタイプは_(A, (B=>B)) => (B=>B)_です。
  • したがって、gは次のとおりです。

    _def g(a: A, f: B=>B): B=>B = 
        (t: B) => f(s(t, a))
    _

これらすべてをまとめる

_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)
}
_

この本を読み進めるレベルでは、より明確で理解しやすいので、実際にはこの形式を好みます。しかし、ソリューションの形式に近づくために、_f0_とgの定義をインライン化できます(gへの入力であり、コンパイラーが推測するため、foldRightの型を宣言する必要はありません)。

_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)
_

シンボルが異なるだけで、まさに問題になっているものです。同様に、foldLeftに関してfoldRightについても同様です。

13
ShreevatsaR

そのコードは、リスト内の要素ごとに1つの関数を使用して、複数の関数オブジェクトをチェーンします。これをより明確に示す例を次に示します。

val f = (a: Int, b: Int) => a+b
val list = List(2,3,4)
println(list.foldLeft(1)(f))

val f1 = (b: Int) => f(b, 2)
val f2 = (b: Int) => f(b, 3)
val f3 = (b: Int) => f(b, 4)
val f4 = (b: Int) => b

val ftotal = f1 andThen f2 andThen f3 andThen f4
println(ftotal(1))

あなたはそれを関数オブジェクトのリンクリストとして想像することができます。値を渡すと、すべての関数を「フロー」します。これは、データフロープログラミングに少し似ています。

7
SpiderPig

この本の著者は、 github/fpinscala ページで適切な説明を提供しています。

1
Tomato