F#を学習しようとしましたが、 fold と reduce を区別しようとすると混乱しました。 Foldは same thing を実行しているようですが、余分なパラメータが必要です。これらの2つの機能が存在する正当な理由があるのでしょうか、それとも異なるバックグラウンドを持つ人々に対応するためにあるのでしょうか? (例:文字列およびC#の文字列)
サンプルからコピーしたコードスニペットを次に示します。
let sumAList list =
List.reduce (fun acc elem -> acc + elem) list
let sumAFoldingList list =
List.fold (fun acc elem -> acc + elem) 0 list
printfn "Are these two the same? %A "
(sumAList [2; 4; 10] = sumAFoldingList [2; 4; 10])
Fold
はアキュムレータの明示的な初期値を取りますが、reduce
は入力リストの最初の要素をアキュムレータの初期値として使用します。
つまり、アキュムレータと結果の型はリスト要素の型と一致する必要がありますが、アキュムレータは個別に提供されるため、fold
が異なる場合があります。これはタイプに反映されます:
List.fold : ('State -> 'T -> 'State) -> 'State -> 'T list -> 'State
List.reduce : ('T -> 'T -> 'T) -> 'T list -> 'T
さらに、reduce
は空の入力リストで例外をスローします。
Leeが言ったことに加えて、reduce
でfold
を定義できますが、(簡単に)逆ではありません:
let reduce f list =
match list with
| head::tail -> List.fold f head tail
| [] -> failwith "The list was empty!"
fold
はアキュムレータの明示的な初期値をとるという事実は、fold
関数の結果がリスト内の値の型と異なる型を持つことができることも意味します。たとえば、タイプstring
のアキュムレータを使用して、リスト内のすべての数値をテキスト表現に連結できます。
[1 .. 10] |> List.fold (fun str n -> str + "," + (string n)) ""
reduce
を使用する場合、アキュムレーターのタイプはリスト内の値のタイプと同じです。つまり、数値のリストがある場合、結果は数値でなければなりません。前のサンプルを実装するには、最初に数値をstring
に変換してから累積する必要があります。
[1 .. 10] |> List.map string
|> List.reduce (fun s1 s2 -> s1 + "," + s2)
それらの署名を見てみましょう:
_> List.reduce;;
val it : (('a -> 'a -> 'a) -> 'a list -> 'a) = <fun:clo@1>
> List.fold;;
val it : (('a -> 'b -> 'a) -> 'a -> 'b list -> 'a) = <fun:clo@2-1>
_
重要な違いがいくつかあります。
reduce
は1つのタイプのエレメントでのみ機能しますが、fold
のアキュムレーターとリストのエレメントは異なるタイプである可能性があります。reduce
を使用すると、最初の要素から始まるすべてのリスト要素に関数f
を適用できます。
f (... (f i0 i1) i2 ...) iN
。
fold
を使用して、アキュムレータf
から開始してs
を適用します。
f (... (f s i0) i1 ...) iN
。
したがって、reduce
は空のリストでArgumentException
になります。さらに、fold
はreduce
よりも汎用的です。 fold
を使用してreduce
を簡単に実装できます。
場合によっては、reduce
を使用する方が簡潔です。
_// Return the last element in the list
let last xs = List.reduce (fun _ x -> x) xs
_
または合理的なアキュムレータがない場合はより便利です:
_// Intersect a list of sets altogether
let intersectMany xss = List.reduce (fun acc xs -> Set.intersect acc xs) xss
_
一般に、fold
は、任意のタイプのアキュムレーターを使用するとより強力です。
_// Reverse a list using an empty list as the accumulator
let rev xs = List.fold (fun acc x -> x::acc) [] xs
_
fold
は、reduce
よりもはるかに価値のある関数です。 fold
に関して多くの異なる関数を定義できます。
reduce
はfold
のサブセットです。
折り畳みの定義:
let rec fold f v xs =
match xs with
| [] -> v
| (x::xs) -> f (x) (fold f v xs )
折り畳みに関して定義された関数の例:
let sum xs = fold (fun x y -> x + y) 0 xs
let product xs = fold (fun x y -> x * y) 1 xs
let length xs = fold (fun _ y -> 1 + y) 0 xs
let all p xs = fold (fun x y -> (p x) && y) true xs
let reverse xs = fold (fun x y -> y @ [x]) [] xs
let map f xs = fold (fun x y -> f x :: y) [] xs
let append xs ys = fold (fun x y -> x :: y) [] [xs;ys]
let any p xs = fold (fun x y -> (p x) || y) false xs
let filter p xs =
let func x y =
match (p x) with
| true -> x::y
| _ -> y
fold func [] xs