-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Stdlib: Add take, drop, split_at, take_while and drop_while to the List module #9968
base: trunk
Are you sure you want to change the base?
Conversation
Spotted this PR just as I was about to create an identical one; thanks! 🙂 I think there's a strong case for adding these functions to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I support this PR, but as usual with stdlib
changes, let's wait for other developers to chime in.
is not satisfied anymore. | ||
*) | ||
|
||
val drop_while : ('a -> bool) -> 'a list -> 'a list |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val drop_while : ('a -> bool) -> 'a list -> 'a list | |
val drop_while : f:('a -> bool) -> 'a list -> 'a list |
This function is morally equivalent to [(take i l, drop i l)]. | ||
*) | ||
|
||
val take_while : ('a -> bool) -> 'a list -> 'a list |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val take_while : ('a -> bool) -> 'a list -> 'a list | |
val take_while : f:('a -> bool) -> 'a list -> 'a list |
[l1] is the prefix of [l] before the index [i], and | ||
[l2] is the suffix of [l] on and after the index [i]. | ||
This function is morally equivalent to [(take i l, drop i l)]. | ||
*) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"since" annotation missing (here and below and in ListLabels
as well).
(** [split_at i l] returns a pair of lists [(l1, l2)], where | ||
[l1] is the prefix of [l] before the index [i], and | ||
[l2] is the suffix of [l] on and after the index [i]. | ||
This function is morally equivalent to [(take i l, drop i l)]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer a more succint wording: [split_at i l] is [(take i l, drop i l)], but more efficient.
|
||
val take_while : ('a -> bool) -> 'a list -> 'a list | ||
(** [take_while p l] returns the prefix of [l] until the predicate [p] | ||
is not satisfied anymore. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the english here a bit unclear. Perhaps something along the lines:
[take_while p l] is [(l1, l2)] where [l = l1 @ l2], [p] is [true] for every element of [l1]
and is [false] for the first element of [l2] (if [l2] is non-empty).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this redrafted explanation does not typecheck: take_while p l
is not a pair. For variety, here's the one that I was about to use:
[take_while p l] is the longest (possibly empty) prefix of [l] containing only elements that satisfy [p].
Personally I am a bit suspicious of the For |
Going back over my code, here's a suggested use-case for each of
I encourage others to give their own examples. Aside from the inefficiency, using drop n l
(* vs. *)
split_at (n + 1) l |> snd
edit (12-01-2021): After some thought & interactions with language newcomers / students, I've seen the error of my ways and would now advise returning |
But then you may want to know whether you should print the ellipsis or not, by checking whether the "rest" of the list/sequence is empty.
Often it is useful to read the header first, to at least check that it corresponds to what you expect. Besides, when you do this kind of work,
That sounds reasonable. |
I was thinking of Regardless, I'm reminded why I don't like presenting use-cases of utility functions and will stop derailing the discussion 🙂 |
I hope you didn't find my pushback-comments taxing; presenting use-cases as you do is very useful, thanks! |
In xapi it's used to limit the size of an error cache: https://github.com/xapi-project/xen-api/blob/b26de9798d2ac9c6e9fc4156906fa6e82ddb96dc/ocaml/xapi/storage_impl.ml#L322, to pad a list of up to a maximum number: For more usages, see https://github.com/search?p=1&q=org%3Axapi-project+%22List.take%22&type=Code |
(Perhaps |
(It's possible to write a trmc |
Looking at this PR again (thanks @dra27 for the reminder):
|
Note that the word "split" is already used by I forgot this earlier, because I always forget that the Stdlib doesn't have I find the name and type of "split_map" a bit odd because
I worry that call-sites of this function would be difficult to understand for those reasons. In another library, this might be a good use of a "continue-or-stop" type: type ('a, 'b) t =
| Continue of 'a
| Stop of 'b although personally I'm not sure that |
Thinking about this more, I agree that (I don't think that the traversal behavior is a problem, it is clear from the type and |
Similar to @kit-ty-kate I recently was in need of The implementation chosen in This PR is a bit old. Would the maintainers (@gasche) like a new one to be made, closing this one? In which case @kit-ty-kate can work on it if they want or I'd be happy to submit one instead. I can commit to implement I'm liking the language a lot so far. Thanks for all the hard work, it's greatly appreciated :) tl;dr: |
I agree with the idea of implementing List functions that mirror Seq functions. I think that proposing I expect the same behavior on invalid inputs between List and Seq. Regarding recursion vs. acc-and-reverse, note that Seq and List have different stack-consumption behaviors due to Seq being an on-demand data structure, so different approaches may make sense. We also have the option of using a @craigfe is making a good point that there is a name conflict between |
(Re. |
Hi @gasche, thanks for the quick reply. I agree that |
My vote is for @kit-ty-kate original suggestion of @gasche's suggestion for If @gasche would it be helpful if I documented the current inconsistencies in the functions implemented by |
I recently had the need for a
List.take
function and no such function was available in the standard library.This isn't the first time I needed such functions and I feel like many other people had the need for some of those at some point.
I hope this PR is helpful for those people.
Credit for the function names, and more importantly the test cases, goes to Haskell's
Data.List
documentation: https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-List.html#g:11