Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fold map function for List #8894

Merged
merged 3 commits into from
Nov 7, 2019
Merged

Fold map function for List #8894

merged 3 commits into from
Nov 7, 2019

Conversation

bschommer
Copy link
Contributor

As the title indicates this PR implements the quite common and popular function that combines fold and map. I don't have any strong opinion on the order of arguments and or the name.

@dra27
Copy link
Member

dra27 commented Aug 26, 2019

A few comments, not including an opinion on whether this should go in:

  • Batteries has BatList.fold_left_map (see the discussion when it was added in Add List.fold_map ocaml-batteries-team/batteries-included#734).
  • If merged, ListLabels needs updating too.
  • Order of arguments should match fold_left (which it does - that's a case for having a strong opinion!)
  • Containers also has the function, same signature, but called fold_map
  • Base also has the function, with two variants folding_map (which doesn't return the accumulator) and fold_map (which does)

@bschommer
Copy link
Contributor Author

I took inspiration from the above mentioned implementation, the name is inspired by the recently added filter_map. ListLabels is also updated. As said I don't have a strong opinion on the name or argument order but I would like to have this function included since it is rather common and I implemented it myself often enough to wish it to be in the standard library.

@bschommer
Copy link
Contributor Author

The failing CI build is due to missing changes, I wanted to wait until the name is fixed before adding them.

@@ -42,6 +42,7 @@ let () =
assert (List.compare_length_with ['1'] 1 = 0);
assert (List.compare_length_with ['1'] 2 < 0);
assert (List.filter_map string_of_even_opt l = ["0";"2";"4";"6";"8"]);
assert (List.fold_map (fun a b -> a + b, b) 0 l = (45, l));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, but this test case doesn't check that the map part of fold_map does anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some more tests.

@cfcs
Copy link

cfcs commented Aug 27, 2019

Err, sorry to be back with more annoying bits :)

  1. A lot of the other functions document (in their mli docstrings) whether or not they are tail-recursive so people can avoid using them if they have very long lists.
    Would it make sense to add that for this function too (since the call to List.rev makes it non-tail-recursive)?

  2. Would there be a case for a fold_map_rev which was tail-recursive?

@bschommer
Copy link
Contributor Author

  1. A lot of the other functions document (in their mli docstrings) whether or not they are tail-recursive so people can avoid using them if they have very long lists.
    Would it make sense to add that for this function too (since the call to List.rev makes it non-tail-recursive)?

List.rev is actually tail-recursive.

@dra27
Copy link
Member

dra27 commented Aug 27, 2019

Given that we have rev_map it might be nice to have the faster rev_fold_map as well, but that doesn't have to be done here.

@cfcs
Copy link

cfcs commented Aug 27, 2019

List.rev is actually tail-recursive.

Woops, my bad. Indeed, but it's a bit slower as you noted. :)

@bschommer
Copy link
Contributor Author

Given that we have rev_map it might be nice to have the faster rev_fold_map as well, but that doesn't have to be done here.

I think if we can agree on the name and arguments of the function, we can actually do even a bit more and add the function to all modules that have a map function.

@bschommer
Copy link
Contributor Author

I rebased the branch. I still think this is a useful and often used function and would like to have it included in the standard library.

@bschommer bschommer changed the title [RFC]: Fold map function for List Fold map function for List Oct 22, 2019
@alainfrisch
Copy link
Contributor

Thanks for the contribution.

I agree this is a useful addition. I have a preference for fold_left_map, which is more coherent with the rest of the Stdlib. Even in Seq where the traversal direction is unambiguous, we have fold_left, not just fold.

Does anyone prefer to keep fold_map or push for something else?

we can actually do even a bit more and add the function to all modules that have a map function.

Indeed, one can even generically transform a map function to an accumulating variant:

let map_accu map f acc l =
  let acc = ref acc in
  let g x =
    let new_acc, y = f !acc x in
    acc := new_acc;
    y
  in
  let r = map g l in
  !acc, r

and this shows that providing the function is "just" a convenience for common cases. But let's keep that for later. I find it much less natural for data structures that implements a map but which don't have a canonical traversal ordering. E.g. Map is borderline (its map function specifies the traversal ordering, so that would be ok-ish), but if we had an Hashtbl.map (copying an hashtable, mapping values, with an unspecified traversal ordering), that wouldn't be so good.

@lpw25
Copy link
Contributor

lpw25 commented Oct 25, 2019

+1 to naming it fold_left_map. To me fold means fold_right.

@bschommer
Copy link
Contributor Author

I'm okay with fold_left_map the name then also makes the iteration order more clear.

f:('a -> 'b -> 'a * 'c) -> init:'a -> 'b list -> 'a * 'c list
(** [fold_left_map] is a combination of [fold_left] and [map] hat threads an
accumulator through calls to [f]
@since 4.10.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@since 4.10.0
@since 4.11.0

stdlib/list.mli Outdated
val fold_left_map : ('a -> 'b -> 'a * 'c) -> 'a -> 'b list -> 'a * 'c list
(** [fold_left_map] is a combination of [fold_left] and [map] that threads an
accumulator through calls to [f]
@since 4.10.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@since 4.10.0
@since 4.11.0

Changes Outdated
@@ -56,6 +56,11 @@ OCaml 4.10.0
(Guillaume Munch-Maccagnoni, review by Jacques-Henri Jourdan,
Stephen Dolan, and Gabriel Scherer)

### Standard library:

- #8894: Added List.fold_left_map function combining map and fold.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be moved to current working version (=> 4.11).

@alainfrisch
Copy link
Contributor

LGTM. Could you move the change entry to 4.11 and update the since tags accordingly? Then it should be good to merge if nobody complains.

The fold_map function is quite common combination of fold and map
which allow it to pass an accumulator through map.
@bschommer
Copy link
Contributor Author

I cleaned up the history, changed the target version and add @cfcs and @alainfrisch as reviewer.

Copy link
Contributor

@alainfrisch alainfrisch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, will merge soon if nobody complains.

@bschommer : thanks for your contribution (in general, no need to put efforts rewriting the history since we tend to squash-merge small PRs anyway).

@bschommer
Copy link
Contributor Author

@bschommer : thanks for your contribution (in general, no need to put efforts rewriting the history since we tend to squash-merge small PRs anyway).

No problem.

@alainfrisch alainfrisch merged commit e5ebec7 into ocaml:trunk Nov 7, 2019
@bschommer bschommer deleted the fold-map branch November 12, 2019 12:27
anmolsahoo25 pushed a commit to anmolsahoo25/ocaml that referenced this pull request Dec 9, 2019
The fold_map function is quite common combination of fold and map
which allow it to pass an accumulator through map.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants