Example of the difference between List.fold and List.foldBack

爷,独闯天下 提交于 2019-12-29 07:27:30

问题


My understanding of the difference between List.fold and List.foldBack is that foldBack iterates over its list in a reverse order. Both functions accumulate a result from the items in the list.

I'm having trouble coming up with a good example where it is preferable to foldBack over a list. In the examples I have come up with, the results are the same for both fold and foldBack, if the function logic does the same thing.

[<Fact>]
let ``List.foldBack accumulating a value from the right to the left``() =
    let list = [1..5]       
    let fFoldBack x acc =
        acc - x

    let fFold acc x =
        acc - x

    let foldBackResult = List.foldBack fFoldBack list 0
    let foldResult = List.fold fFold 0 list

    Assert.Equal( -15, foldBackResult ) //  0 - 5 - 4 - 3 - 2 - 1
    Assert.Equal( -15, foldResult ) //      0 - 1 - 2 - 3 - 4 - 5

回答1:


You don't see a difference in your example because you chose a function such that for any x1 and x2:

(acc - x1) - x2 = (acc - x2) - x1

So it doesn't matter in what order you go through the list, you will get the same result.

List construction is an example of function for which it is not the case:

x1 :: (x2 :: acc) <> x2 :: (x1 :: acc)

So the following will yield different results:

List.fold (fun acc x -> x :: acc) [] [1; 2; 3; 4; 5]
// val it : int list = [5; 4; 3; 2; 1]

List.foldBack (fun x acc -> x :: acc) [1; 2; 3; 4; 5] [];;
// val it : int list = [1; 2; 3; 4; 5]

List.fold starts with an empty result list and goes forward through the input, adding each element to the front of the result list; therefore the final result is in the reverse order.

List.foldBack, on the other hand, goes backward through the input; so each element newly added to the front of the result list was itself to the front in the original list. So the final result is the same list as the original.




回答2:


Tarmil's answer has already demonstrated the difference between the two in a good, concise manner. I'm going to give an example that uses a somewhat more complex data type. (Actually, if you ignore the naming then my example is a linked list, but you can imagine how it could be expanded to something much more complex.)

The purpose of fold vs. foldBack isn't necessarily obvious when you are computing a scalar value, but when you start using it to build data structures, it becomes clear that most such structures must be built in a particular direction. This is especially true if you use immutable data structures, since you don't have the option of constructing a node and then updating it to point to another node.

In this example, I've defined a structure for a trivial programming language that does nothing but print numbers. It recognizes two instructions, Print and End. Each print instruction stores a pointer to the next instruction. Thus, a program consists of a chain of instructions, each pointing to the next. (The reason I've used this particular example is because, by executing the program, you demonstrate its structure.)

The program uses three different methods of constructing the program from a list of integers. The first, make_list_printer, is defined recursively with no fold, to demonstrate what we're trying to achieve. The second, make_list_printer_foldBack, uses foldBack to achieve the same result. The third, make_list_printer_fold, uses fold to demonstrate how it produces the wrong result.

I've mostly coded in OCaml, not F#, so I apologize if some of the coding conventions used below aren't really considered proper style in F#. I did test it in the F# interpreter, though, and it works.

(* Data structure of our mini-language. *)
type prog =
| End
| Print of int * prog

(* Builds a program recursively. *)
let rec make_list_printer = function
| [] -> End
| i :: program -> Print (i, make_list_printer program)

(* Builds a program using foldBack. *)
let make_list_printer_foldBack ints =
  List.foldBack (fun i p -> Print (i, p)) ints End

(* Builds a program in the wrong direction. *)
let make_list_printer_fold ints =
  List.fold (fun p i -> Print (i, p)) End ints

(* The interpreter for our mini-language. *)
let rec run_list_printer = function
| End ->
    printfn ""
| Print (i, program) ->
    printf "%d " i;
    run_list_printer program

(* Build and run three different programs based on the same list of numbers. *)
let () =
  let base_list = [1; 2; 3; 4; 5] in
  let a =  make_list_printer           base_list  in
  let b =  make_list_printer_foldBack  base_list  in
  let c =  make_list_printer_fold      base_list  in
  run_list_printer a;
  run_list_printer b;
  run_list_printer c

The output that I get when I run this is:

1 2 3 4 5
1 2 3 4 5
5 4 3 2 1


来源:https://stackoverflow.com/questions/27935999/example-of-the-difference-between-list-fold-and-list-foldback

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!