reason
reason copied to clipboard
Missing `=>` infix operator escape
This PR applies the same solution that we use for /\*
and makes =\>
work.
Because we reuse the solution that is already in place, this is still parsed as =>
in the AST and printed as =>
in the OCaml side.
fixes #1941
Great improvement, @anmonteiro! One alternative approach would be to swap ->
and =>
. Thoughts?
One downside to swapping is that it breaks with how other conflicts are handled between syntaxes.
@hcarty what do you mean?
edit: sorry I hadn't seen @jordwalke's comment.
@jordwalke I personally don't mind it, here are my thoughts on the tradeoffs:
-
=\>
is uglier (subjective) but feels natural given/\*
for example -
=\>
could probably just be listed in this comparison table without any further explanation, whereas->
would maybe need its own little explanation paragraph- Granted, this is an edge case that wouldn't come up to often, but I'm generally in favor of what feels more natural.
Thoughts?
It might be nice to save ->
for Reason's core syntax later on. It's not a valid infix operator currently.
The previously cases of swapping for keywords caused confusion before. Infix operators are not likely to be as confusing when swapped - that's already done for string concatenation for example - but it's still nice to have conversion rules be consistent. Is =>
used often enough to be worth adding an exception?
@jordwalke do you agree with the above? should we go ahead and keep =\>
then?
My original idea of swapping ->
and =>
didn't take into account that people will likely want to implement custom behavior for ->
via ppx. So for that reason, we wouldn't want ->
to be represented by a super common infix operator like =>
(in ocaml). Instead parsing ->
into the more obscure |.
identifier makes it a better candidate for fancy / opinionated transforms.
The reasons for using such a complicated escaping convention for *
and /
had more to do with avoiding lexing ambiguity with comments. Normally, we apply swaps or mappings. What would such a solution look like here?
One approach:
ML's =>
could be printed in Reason as ==>
.
ML's ==>
could be printed in Reason as ===>
.
ML's ===>
could be printed in Reason as ====>
.
The downside to that approach is that operators are extra long etc (even ones like ==>
which don't have an ambiguity must be written as ===>
in Reason).
Another approach is that we can swap the operator =>
and \=>
. Reason parses \=>
into the AST as \=>
. We could make it so \=>
gets "swapped" with =>
where all the other swapping happens. Benefits:
- All the swapping works more or less the same - no lexing changes necessary.
- The leading backslash could be used in many places to represent these compatibility parsings. You never have to question how to escape an operator because it's always the first character.
@jordwalke I really like your 2nd idea. I'll try to prototype something in this PR over the next couple of days.
I think you said in Discord that backslash prefixed operators are not valid in OCaml, which would allow Reason to fix all these compatibility parsings in one go.
A user pointed out that ++
is not currently handled properly when converting from OCaml to Reason (see https://reasonml.chat/t/access-conflicting-infix-operator/805). Could that be captured in this fix as well?
@hcarty the idea I have in mind (and I think it's what Jordan implied) is that \
could be a prefix for all such transformations between OCaml and Reason. Therefore, we'd make it work using \++
.
That solves the problem, right?
@anmonteiro I think so, at least for infix operators.
@jordwalke I think this is ready for another look.
I implemented your suggestion by having any operator preceded with a backslash (\
) be the same operator on the OCaml side without the backslash.
This enables things like:
let (\++) = ...
let (\=>) = ...
The way I made it generic for all backslash-prefixed operators is by still having e.g. \=>
in the AST but printing to OCaml as =>
. This way we don't need to enumerate all the possible operators that need swapping.
The downside of this approach is that converting OCaml code that has e.g. =>
to Reason will look like let (=>) = ...
which is invalid Reason code (but I think that's fine?).
This seems like a nice simplification, with the downside that keeping the \
-prefix form in the AST makes conversion to/from OCaml syntax harder.
@hcarty The problem is only about OCaml -> Reason, not the other way around.
Some examples:
# Reason -> Reason
$ echo 'let (\=>) = (a, b) => a + b' | ./_build/install/default/bin/refmt
let (\=>) = (a, b) => a + b;
$ echo 'let (\++) = (a, b) => a + b' | ./_build/install/default/bin/refmt
let (\++) = (a, b) => a + b;
# Reason -> OCaml
$ echo 'let (\=>) = (a, b) => a + b' | ./_build/install/default/bin/refmt --print ml
let (=>) a b = a + b
$ echo 'let (\++) = (a, b) => a + b' | ./_build/install/default/bin/refmt --print ml
let (++) a b = a + b
# OCaml -> Reason (where the problem is)
$ echo 'let (=>) a b = a + b' | ./_build/install/default/bin/refmt --parse ml
let (=>) = (a, b) => a + b;
$ echo 'let (++) a b = a + b' | ./_build/install/default/bin/refmt --parse ml
let (++) = (a, b) => a + b;
@anmonteiro Sorry, I was thinking of Reason -> OCaml through ocamlformat since refmt doesn't keep comments. I don't think ocamlformat is perfect in that regard either, to be fair. And ocamlformat's Reason -> OCaml conversion currently has some pretty tight version constraints.
@hcarty If you refmt Reason -> Ocaml with refmt and then use ocamlformat, would that work in your case?
@IwanKaramazow Unfortunately not. Going .re -> refmt -> ocamlformat
translates the syntax but last I checked refmt drops comments when outputting OCaml syntax.
Thanks for the work here @anmonteiro! It'd be nice to get this PR approved and merged, since it blocks interoperability with some fairly basic OCaml infix operators, esp. =>
and libraries that use them.
The PR isn't done yet, and I haven't found the time to squeeze it in.
A nasty one I just come across is the (//)
operator used in Fpath - it might need special treatment on top of this :(