Provide let operators in the standard library#2170
Provide let operators in the standard library#2170lpw25 wants to merge 3 commits intoocaml:trunkfrom
Conversation
|
Why not put them directly in the corresponding module, instead of inside a submodule? As I see it, a submodule is useful when the module in question is typically opened to avoid bringing these operators into the global namespace, but my impression is that one won't typically open these modules. |
Submodules are also useful for the reverse situation -- where the submodule is always opened but you don't want to bring in everything in the parent. That is the case here. I literally never want to |
|
Good point, fair enough. |
|
@lpw25 In that case, you could also include the Module that contains operators are called |
I think it would be nice to have a name that can be used in general for bringing domain specific operators like specialized comparisons, arithmetic ops, monadic operators and other infix operators into the toplevel scope. I have the impression that you don't want to distinguish between these usages in practice (it would become a bit too bureaucratic) so having a single good name for all of these would be nice. I'm not saying |
I'm not so sure about this. The To put this another way, if someone wants to use some I'm not sure its completely related, but I also like the parallel between this All this is not to say that I'm against having a standardised name for modules containing all the operators associated with a type (e.g. |
I can see your point. However with the current proposal you are attributing a (monadic) meaning to the name |
True. Maybe a name based on "computation" is better as that is the most commonly used word to cover both monadic and applicative. |
In order to nurture a bit of confusion I propose either |
|
I like An alternative is to have sub-modules named after the meaning of the exported syntax, like To me, |
But then why not put other operators in such a module ? Something @lpw25 finds undesirable (not sure I'm totally convinced by his point though). |
|
Seems like these people would just be asking for trouble. Also, it would be very easy for them to fix their code in such a case. |
I strongly agree with this. Being able to quickly and explicitly bring one or two of these let/match/etc operators into scope would be very helpful for small scale readability. I've found that approach somewhat more readable for new and experienced OCamlers users with the existing monadic syntax ppxs. |
I don't see why. Are you saying that the code will never mix different custom let binders? What about other identifiers? There is already
I find this is a rather weak argument. Things are changing a bit, but we still consider maintaining backward compatibility of the core distribution an important feature, especially when breaking changes cannot be announced in advance with deprecation markers. Even if fixing the code base is easy enough, this requires intervention from the package author and from OPAM package maintainers (patching the code, creating a new release, making sure the previous one is marked as incompatible with the new OCaml, etc). If that libraries happened to be used by other OPAM packages, we can end up with breaking a good part of the eco-system for some time simply because we add more identifiers. |
No, I'm just saying that I think people should explicitly bind their binders when they do that. But that's like, my opinion. As for the "weak argument", it doesn't seem like much of an argument indeed. It just reflects the fact that I don't mind breaking code that shouldn't have been written in the first place. But thinking more about it, I'm not sure that discussed style is as bad as I originally thought, and clearly: I have no idea what style people will adopt. |
|
Could I suggest not getting too bogged down in discussions on what we would do in the hypothetical situation that we wish to extend the signature of these modules. Yes we should probably be more careful in that situation than we would with other modules, but no its not a disaster if we absolutely had to add more operators since in practice it would probably break approximately no code and would give a nice warning about shadowing for cases where it did. The only cases where I could see us adding more values to these modules is for things like a future Would people prefer it if I moved the
Do you mean e.g. having
I'm not completely against that approach, but there are some issues:
It would also probably require having both of: Monad.Syntax:
functor (X : sig type 'a t val bind : ... val product : ... val map : .... end) ->
sig val ( let* ) : ... val ( and* ) : ... val ( let+ ) : ... val ( and+ ) : ... end
Applicative.Syntax:
functor (X : sig type 'a t val product : ... val map : .... end) ->
sig val ( let+ ) : ... val ( and+ ) : ... end |
I think it wouldn't do any harm (it can be added later if it is perceived as missing). I think I would certainly write |
|
|
||
| val ( let+ ) : 'a list -> ('a -> 'b) -> 'b list | ||
|
|
||
| val ( and+ ) : 'a list -> 'b list -> ('a * 'b) list |
There was a problem hiding this comment.
For (and+) and (and* ) at least you must document what operation they are performing. I think that doing it for the let as well would be nice, even though it's clearer from the type.
|
The submodule discussion is very fun but I think there are other things to discuss as well, among which:
(To me having ¹: I don't know offhand of modules with several distinct Monad structures of interest (I'm sure they exist, and probably examples can be built by lifting the case of numbers with several distinct Monoid structure), but all sequential collections (List, Seq, etc.) have a zipWith-style applicative structure that is distinct from their cartesian-product-esque monad. My personal feeling is that we are moving a little too fast in solidifying conventions for rebindable-syntax lets. For extensions and attributes, @alainfrisch did a lot of work on case studies, presenting and discussing several use-cases for the feature, and evaluating each of the proposed syntaxes/designs on them. Where are those examples with the proposed operators here? In the #1947 discussion there were some experience reports from This is not directly related to #2170 itself, but personally I would like to see idiomatic real-world code that uses those new binding operators (we should also agree on a name for the feature, I would suggest "binding operator" rather than "let operator" given that In particular, I have the vague intuition that something is wrong with having |
I would expect individual
I've definitely been playing a bit fast and loose with the subtle difference between these two approaches -- refering to This is also why I've put in both If we instead said that
Sounds good.
No.
What exactly are you hoping to learn from these examples that you wouldn't learn by looking at existing code using I'm unsure about moving more slowly on conventions. On the one hand, if we wait then perhaps some useful conventions will arise organically from the community and we can adopt them in the stdlib. On the other hand, we might wait and then have every different library using different conventions and find it is too late to try and get some uniformity via the stdlib. Note that I already put some of the expected conventions into the manual, so if we do intend to wait and see then we might need to make some changes there as well. |
You are currently in the process of shaping the look and feel of monadic code (become more and more common) in OCaml for the years to come. I would very much like this look and feel to be explained precisely with appropriate examples and design guidelines. People new to monads should have some chance of following but, maybe even more importantly, people used to monadic or applicative code in their own extensions, or Haskell, or Coq/Agda/Idris, should be able to read whatever documentation and conventions we have and infer how to format their own knowledge/practices within those conventions. In that respect, I think that being unclear about Functor vs. Applicative is not good, and that the various design choices you are pushing into the standard library should be clearly presented, discussed, and documented.
This discussion style is too abstract. Could you provide natural examples that look right with the current definition of
Zipping is useful as a modular way to implement
For one, I haven't seen any code using ppx_let. The only natural example I remember seeing is one of cmdliner usage with a custom As a user reading documentation on this feature, here are some questions that I would like to ask:
|
The zipping operations are not part of the usual list monad. The relationship between a monad and its applicative is often given as let+ f = f
and+ x = x in
f xbeing equal to: let* f = f in
let* x = x in
return (f x)and clearly does not hold if you make
Sure, but that doesn't make zipping a structural part of the list monad. Most useful operations on lists are not the core monadic operations.
(That seems pretty dubious to me, why would a list of length 1 be considered infinite but a list of length 2 be considered finite. But either way we're getting off topic here.)
|
|
For the submodule name, I suggest |
Between asking on caml-devel and asking the other maintainers at Jane Street, I have 4 votes for This part of the discussion has somewhat drowned out the other parts of this discussion. I think the remaining points to be addressed are:
|
I'd be in favor, for the same reason as @dbuenzli.
I wouldn't, I like At this point it seems like this is not going to make it in 4.08. Does that sound right to everyone? Finally (though unrelated), it seems like at least some people are very eager to use this syntax: https://discuss.ocaml.org/t/let-syntax-backported-to-ocaml-4-02 |
This sounds reasonable to me: I don't think we have, collectively, taken enough time for discussion on this fairly impactful feature. This could be an issue for our next development meeting. |
| | Ok _, (Error _ as r2) -> r2 | ||
| | Ok v1, Ok v2 -> Ok (v1, v2) | ||
|
|
||
| (** {1 Syntax module} *) |
There was a problem hiding this comment.
This doc comment will be superseded by the interface doc comment, hence it is redundant. Ditto for seq.ml.
|
|
||
| let ( let* ) x f = concat_map f x | ||
|
|
||
| let ( and* ) a b = product a b |
There was a problem hiding this comment.
Why not let ( and* ) = ( and+ )? For that matter, why not let ( and+ ) = product since parameter order doesn't matter?
|
Aside from my nitpicks btw, I really hope this makes it in sooner than later :-) |
|
Developer meeting discussion: too late to push stdlib changes into 4.11 due to imminent branching, but consider this for 4.12.0 |
|
ping @ocaml/caml-devel |
|
will this make it pre-freeze? |
|
We are after the freeze for 4.14/5.0, so the next relevant freeze would be the 5.1 one. |
|
Note: we may want to wait until we have made a decision on #10979 to revisit this one, as they may influence the naming decisions we want here. |
|
Waiting until then seems reasonable. I'm unlikely to put more work into this PR myself, and would be happy for someone else to adopt it. |
|
May I ask why that PR would impact naming decisions in this PR? Isn't the point of choosing uniform letop names like |
|
The discussions in #10979 and #12015 are about fundamental perceived limitations with the current syntax of binding operators. We probably want to take the time to understand those before adding binding operators in the standard library (ossifying on a particular approach).
A learning curve for who? For people who already know about monads / are familiar with monadic syntax, consistent use of
The current PR as it stands does not make functors convenient either, because it does not introduce modules that pack both a type definition and functor/applicative/monad operations. (And if we wanted to do this, I am personally skeptical that |
|
ship it |
|
If anyone is looking for ready-made letops: https://github.com/yawaramin/letops |
|
I think this can be closed. @lpw25's suggestion has been implemented for The second PR also tried to introduce them for Except for the discussion which is widely cross-referenced I don't think it's worth keeping this PR open. |
Indeed, closing PRs which are no longer actionable is good house cleaning. Thanks! |
This PR provides let operators (see #1947) for
List,Option,ResultandSeq. These operators are placed in a sub-moduleSyntaxin each of these modules, which also includes a definition ofreturn.This enables code like:
I'm open to other suggestions for the name
Syntax. I came up with:Computation,Comp,Expression,ExprandSyntax, from which I likedSyntaxthe best. I would say that the name should not be based around the word "let" because I suspect that in the future we will want to put other operators in there (e.g.match*from #1955).Some of the required functions for these operators did not previously exist. None of the modules had their monoidal product and
Listwas missing its bind. Of these I've exposed the product forListandOption, and the bind forList. I didn't expose the product forResultorSeqbecause I didn't feel people were likely to want to use these functions outside of maybe using them asandoperators.The documentation of the newly exposed functions is very minimal so improvements there are welcome.
Decision queued on: