I found async's repeat_until_finished combinator to be very useful:
(** [repeat_until_finished initial_state f] repeatedly runs [f] until [f] returns
[`Finished]. The first call to [f] happens immediately when [repeat_until_finished]
is called. *)
val repeat_until_finished
: 'state
-> ('state -> [ `Repeat of 'state
| `Finished of 'result
] t)
-> 'result t
Would be great for Lwt to have something similar
I agree it's a very useful pattern, but isn't it shorter and cleaner to just write the recursive function directly ?
Maybe we should just document the code pattern.
Async seems to have a bit more magic here than just looping:
let repeat_until_finished state f =
create (fun finished ->
let rec loop state =
f state
>>> function
| `Repeat state -> loop state
| `Finished result -> Ivar.fill finished result
in
loop state)
Although this sort of optimization might not translate to Lwt at all. This function also makes it easier to write stuff that is tail recursive. Although that's probably a minor benefit if we don't have some optimization to go along with it.
@rgrinberg What do you think of while_lwt in #384 vs repeat_until_finished?
Imo it's not as useful because async's primitive let's you change the state
On Tue, May 30, 2017, 2:56 PM Hezekiah M. Carty notifications@github.com
wrote:
@rgrinberg https://github.com/rgrinberg What do you think of while_lwt
in #384 https://github.com/ocsigen/lwt/issues/384 vs
repeat_until_finished?—
You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub
https://github.com/ocsigen/lwt/issues/311#issuecomment-304974297, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAIe-2f7f0zeBhsKhkJtnkDgpSFLhQPbks5r_GZWgaJpZM4Lr3lD
.
They both do with different approaches, right? while_lwt implies some level of imperative state change. repeat_until_finished implies recursion for changing state.
I tend to favor this one, at this point. while_lwt seems like more or less an instance of repeat_until_finished, but with 'state replaced by unit. repeat_until_finished does have a weird interface though, if you are thinking of an actual while loop.
@hcarty would you use repeat_until_finished if it was available, where you now have while_lwt?
Off topic, I know many people don't use the PPX, but @hcarty, can you say your reason? I just want to understand what choice people make and why. Think of it as a survey :)
@aantron I think so. Doing some coding out loud to think about this. All written directly into the text box here so errors are likely.
The repeat_until_finished examples are more verbose. They are also more flexible and less likely to rely on spooky action at a distance.
Thank you @rgrinberg and @aantron for the prodding. It may be worth adding some form of the convenience functions I wrote below if/when something like repeat_until_finished lands.
Using repeat_until_finished
(* An infinite loop *)
let will_run_forever : unit Lwt.t =
Lwt.repeat_until_finished () (
fun () ->
do_a_thing () >>= fun () ->
Lwt.return @@ `Repeat ()
)
(* Repeat a fixed number of times *)
let run_n_times iterations =
Lwt.repeat_until_finished 0 (
fun i ->
if i < iterations then (
do_a_thing () >>= fun () ->
Lwt.return @@ `Repeat (i + 1)
)
else (
Lwt.return @@ `Finished ()
)
)
(* Repeat while monitoring some external state *)
let loop_until_external_state_goes_bad =
Lwt.repeat_until_finished () (
fun () ->
check_external_state >>= function
| Ok state ->
do_a_thing state >>= fun () ->
Lwt.return @@ `Repeat ()
| Error e ->
Lwt.return @@ `Finished e
)
(* Looks easy enough to create while_lwt if useful *)
let while_lwt' cond f =
Lwt.repeat_until_finished () (
fun () ->
cond () >>= function
| true -> f () >>= fun () -> Lwt.return @@ `Repeat ()
| false -> Lwt.return @@ `Finished ()
)
Using while_lwt
(* An infinite loop *)
let will_run_forever =
Lwt.while_lwt (fun () -> Lwt.return_true) (
fun () ->
do_a_thing ()
)
(* Repeat a fixed number of times *)
let run_n_times iterations =
let i = ref 0 in
Lwt.while_lwt (fun () -> Lwt.return (!i < iterations)) (
fun () ->
incr i;
do_a_thing ()
)
(* Repeat while monitoring some external state. Note that this is less flexible
than the repeat_until_finished version, at least without resorting to awful side
effects. *)
let loop_until_external_state_goes_bad =
Lwt.while_lwt check_external_state do_a_thing
@aantron Regarding the PPX - I do use it, and I like it! But it is difficult to explain to people who are new to both OCaml and Lwt. And in this specific case I was working on porting a codebase using lots of Lwt PPX to jbuilder. I think #374 should take care of the current jbuilder/lwt.ppx incompatibilities. But until that lands I wanted to see what it would take to move away from the PPX sugar and how readable the code could be using helper functions instead.
Thanks!
I suggest closing the other issue, as it's ended up as kind-of duplicate of this one, and we are having the in-depth discussion here. I appreciate you opening it though, it's good to know what people want added, and it prods us to hurry up when multiple people request the same thing :)
Regarding jbuilder, ideally that will be released in the next release (so July, 3.1.0), but hopefully it will be in master within a week or a few weeks (I dream about tomorrow, but freaking time limits :/). You might be able to get by with pinning the jbuilder branch.
I suggest closing the other issue
...and you already did it while I was writing that :) Thanks.
Added "on hold" because I think it's best to revisit this after #354 (new lwt.ml) is in.