Різниця між складанням і зменшенням?


121

Намагаючись вивчити F #, але заплутався, намагаючись розрізняти складку та зменшення . Здається, Fold робить те саме, але приймає додатковий параметр. Чи є законна причина існування цих двох функцій чи вони є для розміщення людей з різним походженням? (Напр .: Рядок і рядок у 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])

1
Ви можете писати зменшення та складання у співвідношенні один з одним, наприклад, fold f a lможна записати як reduce f a::l.
Ніл

9
@Neil - Реалізація foldз точки зору reduceскладніше, ніж це - тип акумулятора foldне повинен бути таким самим, як тип речей у списку!
Томаш Петричек

@TomasPetricek Моя помилка, я спочатку мав намір написати це навпаки.
Ніл

Відповіді:


171

Foldприймає явне початкове значення для акумулятора, reduceвикористовуючи перший елемент вхідного списку як початкове значення акумулятора.

Це означає, що акумулятор, тому тип результату повинен відповідати типу списку елементів, тоді як вони можуть відрізнятися, foldоскільки акумулятор надається окремо. Це відображено у типах:

List.fold : ('State -> 'T -> 'State) -> 'State -> 'T list -> 'State
List.reduce : ('T -> 'T -> 'T) -> 'T list -> 'T

Крім того, reduceвикидає виняток у порожній список введення.


Отже, в основному замість цього foldви можете просто додати це початкове значення до початку списку і зробити reduce? Який сенс foldтоді?
Pacerier

2
@Pacerier - Функція накопичувача для fold має інший тип: 'state -> 'a -> 'stateдля fold vs 'a -> 'a -> 'aдля зменшення, тому зменшення обмежує тип результату таким же, як тип елемента. Дивіться відповідь Томаша Петричека нижче.
Лі

178

На додаток до сказаного Лі, ви можете визначити 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)

2
Навіщо визначати скорочення таким чином, щоб воно могло помилитися під час виконання?
Fresheyeball

+1 за приміткою про загальність fold' & its ability to express зменшення ". У деяких мовах є концепція структурної хіральності (Haskell, на яку я дивлюся), яку можна скласти ліворуч або праворуч, візуально зображений на цій вікі ( en.wikipedia.org/wiki/Fold_%28higher-order_function ). За допомогою конструкції ідентичності два інших «основоположних» оператора FP (фільтр і fmap) також реалізовані за допомогою існуючої мовної конструкції першого класу (всі вони - ізоморфні конструкції). ( cs.nott.ac.uk/~pszgmh/fold.pdf ) Див.: HoTT, Принстон (Цей розділ коментарів занадто малий, щоб містити його)
Ендрю

З цікавості .. чи це призведе до зниження продуктивності швидше, ніж у рази, оскільки це менше припущень щодо типів та винятків?
sksallaj

19

Давайте розглянемо їх підписи:

> 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працює лише на одному типі елементів, елементи накопичувача та списку в них 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

18

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

1
Ви визначаєте свій по- foldрізному від List.foldяк типу List.foldIS ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a, але у вашому випадку ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b. Просто, щоб зробити це явним. Крім того, ваш додаток додаток є неправильним. Це спрацює, якщо ви додасте до нього прив'язку, наприклад List.collect id (fold (fun x y -> x :: y) [] [xs;ys]), або заміните мінуси оператором додавання. Таким чином, додавання - не найкращий приклад у цьому списку.
jpe
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.