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

Seq.delay : (unit -> 'a t) -> 'a t #10177

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Open

Conversation

gasche
Copy link
Member

@gasche gasche commented Jan 27, 2021

Quoting the new docstring:

    [delay (fun () -> seq)] has the same elements as [seq], but the expression
    [seq] is only evaluated when the first element is requested.

   For example,
   {[
   let rec of_tree t =
     match t with
     | Leaf v -> Seq.return v
     | Node(l, r) -> Seq.append (of_tree l) (of_tree r)
   ]}
   does not behave as expected, as it traverses its input tree strictly
   even if the output sequence is not forced.
   This function could instead be written as follows:
   {[
   let rec of_tree t =
     Seq.delay @@ fun () ->
     match t with
     | Leaf v -> Seq.return v
     | Node(l, r) -> Seq.append (of_tree l) (of_tree r)
   ]}

Edit: a previous iteration of this PR also proposed flatten=>concat functions, now moved to a separate PR #10352.

@dbuenzli

This comment has been minimized.

Copy link
Contributor

@ulugbekna ulugbekna left a comment

Choose a reason for hiding this comment

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

Interestingly, I was working through parser combinators exercise by F. Pottier and was surprised Stdlib.Seq didn't contain the functions implemented in this PR and Seq.hd/take.

stdlib/seq.ml Outdated Show resolved Hide resolved
@gasche

This comment has been minimized.

@gasche gasche changed the title (Seq.flatten : 'a t t -> 'a t), (Seq.delay : (unit -> 'a t) -> 'a t) (Seq.concat : 'a t t -> 'a t), (Seq.delay : (unit -> 'a t) -> 'a t) Apr 14, 2021
@gasche
Copy link
Member Author

gasche commented Apr 14, 2021

So are we interested in the new functions proposed in this PR? Cc @nojb the indefatigable stdlib curator, @yallop who made good-taste suggestions for Seq extensions, and @c-cube the original author.

@yallop
Copy link
Member

yallop commented Apr 15, 2021

concat is a very useful addition and flat_map is a fine alias. I think we should add them both.

I have some reservations about delay, perhaps mostly around the name, since it seems to make its argument less delayed rather than more.

@gasche
Copy link
Member Author

gasche commented Apr 15, 2021

The way I think of it, Seq.delay (fun () -> foo) delays the computation of foo. If foo is already a value, then it is neither more nor less delayed.

I observed its usefulness by seeing sequence beginners writing "too eager" definitions as in the first of_tree version above.

@gasche
Copy link
Member Author

gasche commented Apr 17, 2021

I proposed Seq.concat (and Seq.concat_map) in #10352 and will restrict the present PR to Seq.delay only.

@gasche gasche changed the title (Seq.concat : 'a t t -> 'a t), (Seq.delay : (unit -> 'a t) -> 'a t) Seq.delay : (unit -> 'a t) -> 'a t Apr 17, 2021
@xavierleroy
Copy link
Contributor

@fpottier : what do you think about this proposal, in light of your recent extensions to module Seq ?

@fpottier
Copy link
Contributor

fpottier commented Nov 5, 2021

Indeed, I have felt the need for this delay function when writing programming exercises involving the "non-determinism monad".

The need for delay is not widely recognized, I think, because most of the folklore on monads has been developed in the Haskell world, where laziness is everywhere by default, so there is no need for delay. In a strict language like OCaml, there can be a need for it.

@xavierleroy
Copy link
Contributor

Thanks @fpottier. Could you and @gasche discuss the matter urgently, so that we know whether to rush Seq.delay in 4.14 ?

@gasche
Copy link
Member Author

gasche commented Nov 5, 2021

Honestly I don't think that it's worth rushing this function in 4.14. we have done without for years, we can do without for a bit more. I would of course be happy if people came to a decision on it (accept or reject), and I guess I should have been more pushy about it (I usually tend to err on the too-pushy side). But I would prefer this discussion to happen in more relaxed conditions than "last days remaining before the release!" -- time to bike-shed the name, etc.

@gasche
Copy link
Member Author

gasche commented Nov 5, 2021

This being said: obviously I agree that this function is useful and I'm happy to rebase the PR if there is consensus in favor of having it. (I would expect such a consensus to come from, for example, @yallop, @fpottier, @c-cube, @dbuenzli and @craigfe.)

@gasche
Copy link
Member Author

gasche commented Nov 5, 2021

Note: Seq.delay (fun () -> seq) can also be written (fun () -> seq ()); the problem with this desugared formulation are that:

  1. the intent is less clear, and in particular
  2. it is harder for beginners to guess (they may not think that they need this unless they are told about delay and why it is useful), and finally
  3. this "breaks the abstraction" of the 'a Seq.t type, as it requires knowing that it is of the form unit -> ...; it would need to be written differently with another definition of sequences, whereas a delay combinator at this type always exists.

@dbuenzli
Copy link
Contributor

dbuenzli commented Nov 5, 2021

As I said in the Seq big bang PR I'm not a heavy Seq users so I don't have that much opinion on the matter. But the name and sig looks good to me.

I was just wondering why this wasn't maybe Seq.delay : 'a t Lazy.t -> 'a t

@c-cube
Copy link
Contributor

c-cube commented Nov 5, 2021

3. this "breaks the abstraction" of the `'a Seq.t` type, as it requires knowing that it is of the form `unit -> ...`; it would need to be written differently with another definition of sequences, whereas a `delay` combinator at this type always exists.

There is no abstraction in Seq already, why does it matter?

@gasche
Copy link
Member Author

gasche commented Nov 5, 2021

@dbuenzli unit -> ... is conceptually simpler, what Seq is already using internally (this is not a lazy/memoized list but a thunked list), and I suspect that it produces simpler / more efficient code in the common usage case where a function literal is passed.

@c-cube While the definition is public, users can do most of their programming without needing to know what the definition is, which means that they are likely to forget the definition. Programming is simpler for them if they can consider it abstract.

@gasche
Copy link
Member Author

gasche commented Dec 13, 2023

I would have had a use of this function today, to build a singleton sequence from a single value, where the value should only be computed when the element of the sequence is forced/demanded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants