Skip to content

Add Option.product and Option.Syntax#13916

Merged
nojb merged 2 commits intoocaml:trunkfrom
nojb:list_seq_option_syntax
May 10, 2025
Merged

Add Option.product and Option.Syntax#13916
nojb merged 2 commits intoocaml:trunkfrom
nojb:list_seq_option_syntax

Conversation

@nojb
Copy link
Copy Markdown
Contributor

@nojb nojb commented Mar 31, 2025

EDIT: contrary to what is written below, following discussion, this PR only adds Option.product and Option.Syntax.

As a follow-up to #13696, this PR adds a Syntax submodule to Option, List and Seq containing binding operators for these modules. Along the way, it also adds Option.product and List.product.

The addition of the Syntax submodules was discussed and approved in principle during the last dev meeting.

@nojb nojb added the stdlib label Mar 31, 2025
@gasche
Copy link
Copy Markdown
Member

gasche commented Mar 31, 2025

There are two sensible interpretations of List.(and+), one is that it performs a cartesian product just like List.( and* ), the other is that it performs a "zipping" product as in the ZipList applicative functor. It is unclear to me which one users would assume, and I find the situation dangerous, in addition to the fact that computing a cartesian product of strict lists is probably a dubious idea performance-wise.

Copy link
Copy Markdown
Contributor

@dbuenzli dbuenzli left a comment

Choose a reason for hiding this comment

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

Looks good to me. Just left a few non essential comments.

@dbuenzli
Copy link
Copy Markdown
Contributor

It's not clear to me whether @gasche's comment is an opposition to add List.Syntax. Personally I don't have a strong opinion on this as I don't see myself using this stuff. But I guess it could perhaps be useful for teaching (?).

@gasche
Copy link
Copy Markdown
Member

gasche commented Mar 31, 2025

It's clear to me that there is a problem here, but I'm not sure how to solve it. The simplest way would be to not provide List.Syntax indeed, or at least to not define ( and+ ).

The other approach I see is to define ( and+ ) as List.combine, so we would have the following behavior:

# let+ x = [1; 2] and+ y = [10; 20] in x+y;;
- : int list = [11; 22]

# let+ x = [1; 2] and+ y = [10; 20; 30] in x+y;;
Exception: Invalid_argument "List.combine".

# let+ x = [1; 2] and* y = [10; 20; 30] in x+y;;
- : int list = [11; 21; 31; 12; 22; 32]

I am not sure whether the same considerations should apply to Seq as well.

@gasche
Copy link
Copy Markdown
Member

gasche commented Mar 31, 2025

(I wonder if @lpw25 has an opinion about this. Should and+ be the cartesian product or the zipping product? Is it okay to encourage people to mix let+ and and* together?)

@kit-ty-kate
Copy link
Copy Markdown
Member

I understand the value of Option.Syntax but i really don't understand the value of the Syntax module for List and Seq.
To me they seem even "dangerous" (hiding costly operations in let-operators sounds like a bad idea to me).

Do you have an example where these are used in a useful manner in the wild?

@nojb
Copy link
Copy Markdown
Contributor Author

nojb commented Mar 31, 2025

(I wonder if @lpw25 has an opinion about this. Should and+ be the cartesian product or the zipping product? Is it okay to encourage people to mix let+ and and* together?)

For what is worth, the definition here coincides with that of #2170.

@dbuenzli
Copy link
Copy Markdown
Contributor

The other approach I see is to define ( and+ ) as List.combine

According to what I read here the zipping product is a notorious example of an applicative functor that is not a monad.

Now since all monads are applicative functors, as a matter of convention I would find it weird that open M.Syntax introduces a let* monad and a let+ applicative functor which is not the one underlying the monad that you bound let* to.

@nojb
Copy link
Copy Markdown
Contributor Author

nojb commented Mar 31, 2025

The other approach I see is to define ( and+ ) as List.combine

According to what I read here the zipping product is a notorious example of an applicative functor that is not a monad.

Now since all monads are applicative functors, as a matter of convention I would find it weird that open M.Syntax introduces a let* monad and a let+ applicative functor which is not the one underlying the monad that you bound let* to.

Incidentally, this same point was also made in #2170 (comment):

In particular, I have the vague intuition that something is wrong with having (and* ) and (and+) defined to be the same thing in all cases. Shouldn't (and+) be more like List.combine than a cartesian product?

No. List.combine is the product for a ZipList style applicative. That applicative has no associated monad and also requires having a return that creates an infinite list instead of a singleton. We could expose it as List.Zip.Syntax, but it is definitely a different thing. (I also personally wouldn't expose it because I think the use of infinite lists in OCaml should not be encouraged -- Seq.t is a much more natural type to use for this kind of applicative).

@nojb
Copy link
Copy Markdown
Contributor Author

nojb commented Apr 10, 2025

Shall we try to move forward here? Two options:

@gasche: do you agree with the point made in #2170 (comment) that the applicative and monadic products should be the same for List (and Seq)?

@gasche
Copy link
Copy Markdown
Member

gasche commented Apr 10, 2025

I'm not fond of having and+ be the cartesian product -- I am not convinced that this is the right design choice -- but on the other hand I do see that there is a reasonable consensus in this direction, and that it is the simplest way forward. Please feel free to move along without my approval on this specific point.

Note: re-reading the discussion of #2170, I realize that Leo proposed to have (and) as an operator that would be used when desugaring both let+ and let*. At the time I voted for and+ and and*, but upon reflection I wish we had gone for (and) to make this whole question go away.

I might propose a List.Zip.Syntax submodule at some point. I view ziplist as a length-indexed family of applicatives, rather than a single applicative whose product silently cuts to the shortest length (which is dubious) and whose return builds an infinite list (which is very dubious). If there was a return, it should therefore be of type int -> 'a -> 'a list, essentially a List.make. We could also just give up on having a return operation, given that people already build lists by hand anyway.

@dbuenzli
Copy link
Copy Markdown
Contributor

Personally I would rather be in favour of just adding Option unless there is evidence that List.t and Seq.t are actually in use with the semantics this PR seeks to establish.

@gasche
Copy link
Copy Markdown
Member

gasche commented Apr 10, 2025

I believe that we all agree that let+, let* and and* have the semantics that we would expect. (Remark: for lists, and* is fairly inefficient, and it is better to just chain let* in sequence. So maybe and* only gets half a point.) Personally I wouldn't mind having these three operators available, just not and+. (Ideally, if we go that route, there should be a note in the implementation that explains its absence.)

@dbuenzli
Copy link
Copy Markdown
Contributor

My point is rather not adding stuff that ends up being mostly unused.

@nojb nojb force-pushed the list_seq_option_syntax branch from 7a5924e to eb0616d Compare April 10, 2025 08:03
@nojb nojb changed the title Add {Option,List}.product and {Option,List,Seq}.Syntax Add Option.product and Option.Syntax Apr 10, 2025
@nojb
Copy link
Copy Markdown
Contributor Author

nojb commented Apr 10, 2025

OK, let's stick to Option alone as a first step, and we can kick the discussion for List and Seq down the road. I adapted the PR accordingly (and the PR description).

@nojb nojb force-pushed the list_seq_option_syntax branch from eb0616d to 378eaf8 Compare April 26, 2025 08:12
@nojb
Copy link
Copy Markdown
Contributor Author

nojb commented Apr 26, 2025

@gasche Friendly ping: are you willing to approve the PR in its current state?

Copy link
Copy Markdown
Member

@gasche gasche left a comment

Choose a reason for hiding this comment

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

I approve with the reduced scope, but have suggestions on the proposed mli documentation which I find too terse for people who don't already know binding operators.

module Syntax : sig

val ( let* ) : 'a option -> ('a -> 'b option) -> 'b option
(** [( let* )] is {!Option.bind}. *)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would prefer if API docs for binding operators would show the syntax in use, or at least if they would refer explicitly to a chapter in the manual that explains this. Currently the documentation is obscure if you don't already know binding operators, and the syntax is not self-discoverable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added a pointer to the relevant manual section. I did the same for Result.Syntax. Examples would be nice, but it will probably be a more sizeable task and better taken up on its own. Please yell if you are not happy with the current state of the PR. Otherwise I am planning to merge by end of day.

@nojb nojb force-pushed the list_seq_option_syntax branch 2 times, most recently from 7181fde to 5056346 Compare May 9, 2025 11:57
@gasche
Copy link
Copy Markdown
Member

gasche commented May 9, 2025

This needs a second approval from a maintainer. Would @Octachron, @dra27, @OlivierNicole possibly have an opinion? (Note: Daniel formally approved the PR, so one option is to green-approve on his behalf.)

Copy link
Copy Markdown
Member

@dra27 dra27 left a comment

Choose a reason for hiding this comment

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

LGTM!

@nojb nojb force-pushed the list_seq_option_syntax branch from 5056346 to 1bbc9c5 Compare May 10, 2025 13:59
@nojb
Copy link
Copy Markdown
Contributor Author

nojb commented May 10, 2025

LGTM!

Thanks for the approval, will merge once CI passes.

@nojb nojb merged commit 90d4546 into ocaml:trunk May 10, 2025
23 of 24 checks passed
@nojb nojb deleted the list_seq_option_syntax branch May 10, 2025 14:32
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.

5 participants