Diferença entre dobrar e reduzir?


121

Tentando aprender F #, mas fiquei confuso ao tentar distinguir entre fold e reduzir . Fold parece fazer a mesma coisa, mas requer um parâmetro extra. Existe uma razão legítima para essas duas funções existirem ou elas existem para acomodar pessoas com diferentes origens? (Por exemplo: String e string em C #)

Aqui está o trecho de código copiado da amostra:

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
Você pode escrever reduzir e dobrar em termos um do outro, por exemplo, fold f a lpode ser escrito como reduce f a::l.
Neil

9
@ Neil - Implementar foldem termos de reduceé mais complicado do que isso - o tipo de acumulador de foldnão precisa ser o mesmo que o tipo de coisas da lista!
Tomas Petricek

@TomasPetricek Meu erro, eu pretendia originalmente escrever o contrário.
Neil

Respostas:


171

Foldusa um valor inicial explícito para o acumulador enquanto reduceusa o primeiro elemento da lista de entrada como o valor inicial do acumulador.

Isso significa que o acumulador e, portanto, o tipo de resultado devem corresponder ao tipo de elemento da lista, enquanto eles podem diferir, foldpois o acumulador é fornecido separadamente. Isso se reflete nos tipos:

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

Além disso, reducelança uma exceção em uma lista de entrada vazia.


Então, basicamente, em vez de fazer fold, você pode simplesmente adicionar esse valor inicial ao início da lista e fazer reduce? Qual é o objetivo foldentão?
Pacerier

2
@Pacerier - A função acumuladora de dobra tem um tipo diferente: 'state -> 'a -> 'statepara dobra vs 'a -> 'a -> 'apara reduzir, portanto, reduz o tipo de resultado ao mesmo que o tipo de elemento. Veja a resposta de Tomas Petricek abaixo.
Lee

178

Além do que Lee disse, você pode definir reduceem termos de fold, mas não (facilmente) o contrário:

let reduce f list = 
  match list with
  | head::tail -> List.fold f head tail
  | [] -> failwith "The list was empty!"

O fato de foldusar um valor inicial explícito para o acumulador também significa que o resultado da foldfunção pode ter um tipo diferente do tipo de valores na lista. Por exemplo, você pode usar o acumulador do tipo stringpara concatenar todos os números em uma lista em uma representação textual:

[1 .. 10] |> List.fold (fun str n -> str + "," + (string n)) ""

Ao usar reduce, o tipo de acumulador é igual ao tipo de valores na lista - isso significa que, se você tiver uma lista de números, o resultado deverá ser um número. Para implementar a amostra anterior, você teria que converter os números em stringprimeiro e depois acumular:

[1 .. 10] |> List.map string
          |> List.reduce (fun s1 s2 -> s1 + "," + s2)

2
Por que definir reduzir de forma que possa ocorrer erro no tempo de execução?
Fresheyeball

+1 para a nota sobre a generalidade de fold' & its ability to express reduzir '. Algumas línguas têm um conceito de quiralidade estrutural (Haskell, estou olhando para você). Você pode dobrar para a esquerda ou para a direita, visualmente representado neste wiki ( en.wikipedia.org/wiki/Fold_%28higher-order_function ). Com uma construção de identidade, os outros dois operadores FP "fundamentais" (filter e fmap) também são implementáveis ​​com uma construção de linguagem de primeira classe `fold 'existente (todas elas são construções isomórficas). ( Cs.nott.ac.uk/~pszgmh/fold.pdf ) Ver: Hott, Princeton (Esta seção de comentários é demasiado pequeno para conter ..)
Andrew

Por curiosidade .. isso tornaria o desempenho da redução mais rápido que o dobro, porque está sob menos suposições sobre tipos e exceções?
Sksallaj 28/06/19

19

Vejamos suas assinaturas:

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

Existem algumas diferenças importantes:

  • Embora reducefuncione apenas em um tipo de elemento, o acumulador e os elementos da lista foldpodem estar em tipos diferentes.
  • Com reduce, você aplica uma função fa todos os elementos da lista, começando pelo primeiro:

    f (... (f i0 i1) i2 ...) iN.

    Com fold, você aplica fa partir do acumulador s:

    f (... (f s i0) i1 ...) iN.

Portanto, reduceresulta em uma ArgumentExceptionlista vazia. Além disso, foldé mais genérico que reduce; você pode usar foldpara implementar reducefacilmente.

Em alguns casos, o uso reduceé mais sucinto:

// Return the last element in the list
let last xs = List.reduce (fun _ x -> x) xs

ou mais conveniente se não houver um acumulador razoável:

// Intersect a list of sets altogether
let intersectMany xss = List.reduce (fun acc xs -> Set.intersect acc xs) xss

Em geral, foldé mais poderoso com um acumulador de um tipo arbitrário:

// Reverse a list using an empty list as the accumulator
let rev xs = List.fold (fun acc x -> x::acc) [] xs

18

foldé uma função muito mais valiosa do que reduce. Você pode definir muitas funções diferentes em termos de fold.

reduceé apenas um subconjunto de fold.

Definição de fold:

let rec fold f v xs =
    match xs with 
    | [] -> v
    | (x::xs) -> f (x) (fold f v xs )

Exemplos de funções definidas em termos de dobra:

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
Você define o seu foldde forma diferente de List.foldcomo o tipo de List.foldé ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a, mas no seu caso ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b. Apenas para deixar explícito. Além disso, sua implementação do append está errada. Funcionaria se você adicionar uma ligação a ela, por exemplo List.collect id (fold (fun x y -> x :: y) [] [xs;ys]), ou substituir contras pelo operador de acréscimo. Portanto, anexar não é o melhor exemplo nesta lista.
Jpe # 16/15
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.