Skip to content

named let syntax#10979

Open
EduardoRFS wants to merge 1 commit intoocaml:trunkfrom
EduardoRFS:named-let-syntax
Open

named let syntax#10979
EduardoRFS wants to merge 1 commit intoocaml:trunkfrom
EduardoRFS:named-let-syntax

Conversation

@EduardoRFS
Copy link
Copy Markdown
Contributor

@EduardoRFS EduardoRFS commented Feb 1, 2022

What

ReasonML syntax in the past provide an additional feature to the current let syntax, where you could use alphanumeric.

This is a very useful feature if you're dealing with many monads at any point in time or just to have them available globally on a local stdlib. At work we use this under some bindings like let.{some,ok,await} but as we wanted to migrate to OCaml syntax we end up developing a ppx to workaround this.

Problems

As pointed out by @Octachron this is actually a breaking change as let+x = 1 is currently parsed as let+ x = 1

But it was also pointed out that this is not a breaking change without precedents, as that also happened for custom indexing operators < m: 'a.?x:int -> ... >. I would argue that let+x working nowadays seems more like an artifact of the implementation than something desirable .

The . on the other hand is a new character, so not a breaking changes and it fits kind of well with the remaining of the language as it gives this notion of accessing the content of the monad.

So this PR limits the patch to let<symbol>(. <lident>) and let.<lident> is allowed.

Examples

let ( let.some ) = Option.bind
let some_five =
  let.some four = Some 4 in
  Some (four + 1)

let ( let.await ) = Lwt.bind
let ( and.await ) = Lwt.both

let () =
  let.await () = Lwt_unix.sleep 1
  and.await () = Lwt_unix.sleep 1  in
  (* both timers are executed in parallel *)
  print_endline "tuturu"

@EduardoRFS EduardoRFS changed the title [feature] Named let syntax named let syntax Feb 1, 2022
@gasche
Copy link
Copy Markdown
Member

gasche commented Feb 1, 2022

Quick comments:

  • I would expect let*.some instead, and let+.some.
  • If I understand correctly, you also support and.some? Please include it in examples.
  • Previously ! was intentionally ruled out due to let! looking like a shadowing-related keyword, you probably should not reintroduce it.
  • We (and you) are busy with multicore-related stuff right now, so I won't spend much time discussing this PR before the Multicore stuff settles a bit.

@EduardoRFS
Copy link
Copy Markdown
Contributor Author

If I understand correctly, you also support and.some? Please include it in examples.

Examples of and.await added and test for and.pair added

I would expect let*.some instead, and let+.some.

That's fair, I'm personally in the team of let+ should not be used, but adding support to it is harmless, and it also allows to add support to composed names let.await.some

Previously ! was intentionally ruled out due to let! looking like a shadowing-related keyword, you probably should not reintroduce it.

! removed, completely fair.

We (and you) are busy with multicore-related stuff right now, so I won't spend much time discussing this PR before the Multicore stuff settles a bit.

I'm just hoping that we could have this without too much discussion. But ... as @Octachron pointed out, it's not simple and there is some problems, I will edit the original post with more problems.

@gasche
Copy link
Copy Markdown
Member

gasche commented Feb 1, 2022

Yeah right, a new syntax without too much discussion :-)

['!' '$' '%' '&' '*' '+' '-' '.' '/' ':' '<' '=' '>' '?' '@' '^' '|' '~']
let dotsymbolchar =
['!' '$' '%' '&' '*' '+' '-' '/' ':' '=' '>' '?' '@' '^' '|']
let letopsymbolchar = dotsymbolchar | ['.' '\\' 'a'-'z' 'A'-'Z' '_' '0'-'9']
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.

The character \ is only used for escape sequence, it seems more coherent to not include it here.

@Octachron
Copy link
Copy Markdown
Member

I agree that the syntax looks fine, we could also be more conservative and allow just the let.[:alpha:]+ and and.[:alpha:]+ subcase (which doesn't change the tokenisation of existing code).

@gasche
Copy link
Copy Markdown
Member

gasche commented Jul 19, 2022

Personally I like the idea of being able to combine operator symbols and alphabetic names, eg. let+.some as mentioned. I think @Octachron your point is that requiring a dot before the alphabetic part avoids the ambiguity with let<op> <ident>, so we could restrict the alphanumeric part to only start be allowed after a dot -- then let<op>(.<alpha>)* would also be supported. Does that sound reasonable?

Even if we don't start parsing let+x as a single token, it would probably be wise to change the lexer to forbid the current parsing as let+ x, to keep free syntactic space for later and make the restriction as soon as possible after the introduction of binding operators.

@Octachron
Copy link
Copy Markdown
Member

My point was indeed that requiring a . dot before the alphanumeric part removes the minor conflict with the current syntax. Another potentially interesting restriction would be to constrain the alphabetic part to a lowercased identifier to keep some room for a possibly quantified name

let+.List.app1 x = in

interpreted as List.(let+.app1) which fits with the idea that the right hand side of a . is a qualified path.

@gasche
Copy link
Copy Markdown
Member

gasche commented Nov 20, 2022

Ah, so maybe your idea is to allow let.<lident> now and maybe let.<longident> later (where <longident> is (<uident>.)*<lident>)? I like the idea.

@Octachron
Copy link
Copy Markdown
Member

@EduardoRFS Since the merge windows for OCaml 5.1 is open, what are your thoughts on starting by restricting the syntax first to let<symbol>(. <lident>) or let.<lident> (which fits your examples)?

@EduardoRFS EduardoRFS force-pushed the named-let-syntax branch 3 times, most recently from 4006bbc to 3c02fd8 Compare January 19, 2023 17:27
@EduardoRFS
Copy link
Copy Markdown
Contributor Author

EduardoRFS commented Jan 19, 2023

Did the suggested changes, also removed [A-Z] and \ from the list.

But I wonder if allowing for let*.some is desirable, it could also be named let.some* and it would work, so maybe only let. should be allowed to be named for now? But it is a very small advantage, so I'm fine with both cases.

@Octachron
Copy link
Copy Markdown
Member

A potential issue with let.option+ is that after a lower-case non-keyword ident a symbol is normally the start of a new token, so people might read it as let.option + for a time.

Moreover, since the let+ and let* operators are not going away for compatibility reason, I think it makes sense to stick to
let+.option.

| '#' symbolchar_or_hash + as op
{ HASHOP op }
| "let" kwdopchar dotsymbolchar * as op
| "let" kwdopchar? '.' letdotopsymbolchar * as op
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.

For my conservative option, I had in mind something more akin to:

 | "let" kwdopchar? '.' lowercase identchar* as op

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.

Why not extending letdotopsymbolchar to include uppercase and saying only the first one must be a initial one? Yours approach would not allow for let.some.map for example.

But the current one allow for some weird stuff such as let.some.....

| "let" kwdopchar? '.' lowercase letdotopsymbolchar* as op

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.

The token . is generally used for projection or paths, typically I would expect let.some.map and let.some.bind to be somehow related to a let.some but that is not the case here.

It seems fine to me to be conservative and first limit ourselves to the syntax that we really want like let.option, let.some_map, let.ok, let.error_map, or let.app_list and leave some rooms for future options. After all, it is far easier to relax the syntax constraint in the future compared to trying to deprecate an existing but nearly totally unused syntax.

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.

In case additional opinions can help: I agree that I prefer to allow let*.some but rule out let.some* for now (so: keeping operator symbols adjacent to the keyword, not in other places), but I would be fine with allowing let.some.map as well. It is probably wise to disallow Uidents for now, to leave room for a proper qualification semantics in the future, and generally to try to under-approximate rather than over-approximate the allowed utterances, to keep freedom for future extensions.

Copy link
Copy Markdown
Contributor Author

@EduardoRFS EduardoRFS Feb 2, 2023

Choose a reason for hiding this comment

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

I made the changes as they do make sense and I will make the discussion worse.

The usage of . is probably bad, as . commonly has the notion of accessing something.

I know that the analogy here is that let.some means access the Some and do some stuff inside of it. But for that meaning as a language designer I would much rather allow only let*.some. And in the future I would do the breaking change and not add the ., so let*some, maybe add a lexer warning?

But I know that we're being victim's of the Wadler's Law, and as an OCaml developer, I think let.some is way better than let*.some, even thought it is a single token. It just reads naturally.

Worst case scenario we will need a typer hack to workaround another bit of syntax that is half broken and that at some point will need a deprecation policy.

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.

Is let.some intended to be the map or the bind for the option type? I'm not sure.

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.

That's a convention up to the user, in the same way that * | + are conventions, I personally don't ever add map so let.some is always bind.

My usage is a pairing of let.some for bind and some for return.

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.

let.some x = 1 in some (x + 2)

Copy link
Copy Markdown
Member

@Octachron Octachron left a comment

Choose a reason for hiding this comment

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

I am approving for the record the current version of this PR: this is a minimal extension to the let binding operator that covers the extension that the people involved in the discussion really want.

Moreover this version has no incompatibility with existing code since the new lexer rules for LETOP are currently lexed to LET DOT LIDENT or LETOP DOT LIDENT which are not valid in the grammar. In fact, the change could be lifted from the lexer to the parser to make this point more self-evident.

Since this is a syntax change, I will leave one week reaction time for possible objections.

@gasche
Copy link
Copy Markdown
Member

gasche commented Feb 16, 2023

If I read the lexer change correctly, currently let* and let** are accepted as binding operators, and after the PR let*.some will be accepted but let**.some will be rejected. This seems surprising/arbitrary/wrong to me. My high-level understanding of the grammar from the discussion was rather <keyword> <binding-operator-symbols> . <lident>.

@gasche
Copy link
Copy Markdown
Member

gasche commented Feb 17, 2023

i had the impression that the syntax change would benefit from a wider discussion, so I created #12015 to summarize and discuss some feedback on this PR that I had received.

My understanding from the discussion so far is that there isn't a strong consensus yet in favor of the language change, and we probably want to wait a bit to see how the discussion evolves.... sorry.

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.

4 participants