[解決済み] foldrを使ったfoldlの書き方
質問
で 実世界のハスケル 第4章をご覧ください。 関数型プログラミング :
foldrでfoldlを書く。
-- file: ch04/Fold.hs
myFoldl :: (a -> b -> a) -> a -> [b] -> a
myFoldl f z xs = foldr step id xs z
where step x g a = g (f a x)
上記のコードは私を大いに混乱させ、誰かが dps が意味のある名前で書き直してくれたので、少しは分かりやすくなりました。
myFoldl stepL zeroL xs = (foldr stepR id xs) zeroL
where stepR lastL accR accInitL = accR (stepL accInitL lastL)
Jef Gは、例を示し、基礎となるメカニズムを段階的に示すことで、素晴らしい仕事をしました。
myFoldl (+) 0 [1, 2, 3]
= (foldR step id [1, 2, 3]) 0
= (step 1 (step 2 (step 3 id))) 0
= (step 1 (step 2 (\a3 -> id ((+) a3 3)))) 0
= (step 1 (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2))) 0
= (\a1 -> (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (\a3 -> (+) a3 3) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (+) ((+) a2 2) 3) ((+) a1 1)) 0
= (\a1 -> (+) ((+) ((+) a1 1) 2) 3) 0
= (+) ((+) ((+) 0 1) 2) 3
= ((0 + 1) + 2) + 3
しかし、私はまだそれを完全に理解することはできません、以下は私の質問です。
- id 関数は何のためにあるのですか。何のための役割なのでしょうか?なぜここでそれが必要なのでしょうか?
- 上記の例では、id関数はラムダ関数内のアキュムレータですか?
-
foldrのプロトタイプは
foldr :: (a -> b -> b) -> b -> [a] -> b
で、最初のパラメータは2つのパラメータを必要とする関数ですが、myFoldlの実装ではステップ関数が3つのパラメータを使用しています。
どのように解決するのですか?
いくつかの説明が必要です。
id機能は何のためにあるのでしょうか?何のための役割なのでしょうか?なぜここでそれが必要なのでしょうか?
id
は
アイデンティティ機能
,
id x = x
で関数の連鎖を構築する際にゼロと等価なものとして使用されます。
関数構成
,
(.)
. あなたはそれを見つけることができます
で定義されている、前奏曲の
.
上記の例では、id関数はラムダ関数内のアキュムレータ?
アキュムレータは、関数の適用を繰り返すことで構築されていく関数です。アキュムレータに名前を付けているので、明示的なラムダはありません。
step
. 必要ならラムダを付けて書いてもいい。
foldl f a bs = foldr (\b g x -> g (f x b)) id bs a
または グラハム・ハットンならこう書くだろう :
<ブロッククオート
5.1節
foldl
演算子
では、一般化して
suml
の例から一般化して、標準的な演算子
foldl
で、リストの要素を左から右の順に処理する、関数
f
を使って値を組み合わせ、さらに値
v
を開始値として使用します。
foldl :: (β → α → β) → β → ([α] → β)
foldl f v [ ] = v
foldl f v (x : xs) = foldl f (f v x) xs
この演算子を使って
suml
は単に
suml = foldl (+) 0
. 他の多くの関数も,簡単な方法で
foldl
. 例えば,標準的な関数
reverse
を使って再定義することができます。
foldl
を使って次のように再定義します。
reverse :: [α] → [α]
reverse = foldl (λxs x → x : xs) [ ]
この定義は、非効率的な append 演算子の使用を避けるため、fold を使用したオリジナルの定義よりも効率的です。
(++)
を使用しないため、リストに対してより効率的です。
前節の計算を単純に一般化したもので、関数
suml
を再定義する方法を示します。
foldl
の観点から
fold
:
foldl f v xs = fold (λx g → (λa → g (f a x))) id xs v
これに対して,再定義された
fold
の観点から
foldl
という事実のために
foldl
はリスト引数の末尾が厳密ですが
fold
はそうではない。に関する多くの有用な「二元性定理」があります。
fold
と
foldl
といった演算子、そして特定のアプリケーションに最も適した演算子を決定するためのいくつかのガイドラインがあります (Bird, 1998)。
foldrのプロトタイプはfoldr :: (a -> b -> b) -> b -> [a] -> b
Haskellのプログラマなら
型
の
foldr
は
(a -> b -> b) -> b -> [a] -> b
.
で、最初のパラメータは2つのパラメータを必要とする関数ですが、myFoldlの実装ではステップ関数が3つのパラメータを使用しているので、私は完全に混乱しています。
これは紛らわしくて不思議ですね
これは混乱するようで不思議です!トリックを施し、アキュムレータを関数に置き換え、それを初期値に適用して結果を得ます。
Graham Huttonは、次のようにトリックを説明します。
foldl
を
foldr
に変換する。の再帰的な定義を書き下すことから始めます。
foldl
:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v [] = v
foldl f v (x : xs) = foldl f (f v x) xs
の静的引数変換でリファクタリングします。
f
:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v xs = g xs v
where
g [] v = v
g (x:xs) v = g xs (f v x)
では、次のように書き換えてみましょう。
g
を浮き上がらせるように
v
を内側に浮かせる。
foldl f v xs = g xs v
where
g [] = \v -> v
g (x:xs) = \v -> g xs (f v x)
と考えるのと同じです。
g
を関数を返す1つの引数の関数として考えるのと同じです。
foldl f v xs = g xs v
where
g [] = id
g (x:xs) = \v -> g xs (f v x)
ここで
g
のように、リストを再帰的に走査する関数に、ある関数を適用します。
f
. 最終的な値は恒等式関数で、各ステップの結果も同様に関数になります。
しかし
を使えば、リストに対する非常に似た再帰的な関数が既に手元にあります。
foldr
!
2 fold演算子
この演算子は
fold
演算子は再帰理論(Kleene, 1952)に起源を持ち、一方で のfold
をプログラミング言語の中心概念として使うようになったのは、APLのreduction演算子(Iverson, 1962)、そして後にFPのinsertion演算子(Backus, 1978). Haskell ではfold
演算子は次のように定義できる。fold :: (α → β → β) → β → ([α] → β) fold f v [ ] = v fold f v (x : xs) = f x (fold f v xs)
つまり、ある関数が与えられたとき
f
型のα → β → β
という型と、値v
型のβ
である場合、関数fold f v
型のリストを処理します。[α]
型の値を与えるためにβ
という型の値を与える。 コンストラクタを[]
をリストの末尾にある値v
であり、各コンストラクタは(:)
という関数によってリスト内のf
. このようにfold
演算子はリストを処理するための単純な再帰パターンをカプセル化します。この場合、リストに対する2つのコンストラクタは単に他の値や関数に置き換えられます。リストに関する多くのよく知られた関数はfold
.
これは、非常によく似た再帰的なスキームのように見えます。
g
関数と非常によく似た再帰的スキームのように見えます。手近なマジック(Bird、Meertens、Malcolm)をすべて使って、特別なルールである
折り畳みの普遍的性質
という特別なルールを適用する。
g
の2つの定義間の等価性である。
g [] = v g (x:xs) = f x (g xs)
もし
g = fold f v
つまり、折り畳みの普遍的な性質は、次のように述べています。
g = foldr k v
ここで
g
は二つの方程式と等価でなければなりません。
k
と
v
:
g [] = v
g (x:xs) = k x (g xs)
先ほどのfoldlの設計から、私たちは
v == id
. しかし、2番目の式では
になります。
を計算する。
の定義は
k
:
g (x:xs) = k x (g xs)
<=> g (x:xs) v = k x (g xs) v -- accumulator of functions
<=> g xs (f v x) = k x (g xs) v -- definition of foldl
<= g' (f v x) = k x g' v -- generalize (g xs) to g'
<=> k = \x g' -> (\a -> g' (f v x)) -- expand k. recursion captured in g'
という計算された定義を代入すると
k
と
v
は
というfoldlの定義が得られます。
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v xs =
foldr
(\x g -> (\a -> g (f v x)))
id
xs
v
再帰的な
g
はfoldrコンビネータに置き換えられ、アキュムレータは
f
の連鎖によって構築される関数になります(右折りではなく左折り)。
これは確かにやや高度なので、この変換を深く理解するために 折り返しの普遍的な性質 を深く理解するには、以下のリンクにある Hutton のチュートリアルをお勧めします。
参考文献
- Haskell Wiki。foldrとしてのfoldl
- foldの普遍性と表現力についてのチュートリアル Graham Hutton, J. Functional Programming 9 (4): 355-372, 1999年7月.
- マルコム、G. 代数的なデータ型とプログラム変換。 博士論文、フローニンゲン大学。
関連
-
[解決済み] RustのtraitとHaskellのtypeclassの違いは何ですか?
-
[解決済み] foldrとfoldl(またはfoldl')の意味するところ
-
[解決済み] 無限リストでのfoldlとfoldrの動作
-
[解決済み] リストからn番目の要素を得るには?
-
[解決済み] このフィボナッチ関数はどのようにメモされているのですか?
-
[解決済み] Haskellのmapにはfmapがあるのに、何の意味があるのだろう?
-
[解決済み] Haskellプログラムのパフォーマンス解析ツール
-
[解決済み] インデックス付きモナドとは?
-
[解決済み] なぜHaskellは "Best Imperative Language "と呼ばれるのか?
-
[解決済み] foldlは末尾再帰的なのに、なぜfoldrはfoldlより高速に動作するのか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 解釈の仕方 (Eq a)
-
[解決済み] 末尾再帰とは何ですか?
-
[解決済み] IntとIntegerの違いは何ですか?
-
[解決済み] Haskell における `mod` と `rem` の違い
-
[解決済み] Haskellにおける "リフティング "とは?
-
[解決済み] リーダーモナドの目的は何ですか?
-
[解決済み] Haskell エラー 入力 `=' のパースエラー
-
[解決済み] Real World Haskellのどの部分が今となっては時代遅れ、あるいはバッドプラクティスと考えられているのでしょうか?
-
[解決済み] fixの使い方、効果について教えてください。
-
[解決済み] mtl、トランスフォーマー、monads-fd、monadLib、そして選択のパラドックス