syntax
syntax copied to clipboard
Clarification regarding infix operators usage
As far as I know, infix operators are banned from the new syntax, but it seems operators declared in OCaml/old ReasonML can be used in the new syntax (I tried recently with some common operators like <$>
<|>
>=>
, all worked as expected).
Since we have a middle sized application with ~1000 modules, and we use cutom operators quiete extensively, I wanted to get some clarification regarding the usage of custom operators. Will this be banned eventually?
(Also, and for what it's worth, i really do hope custom operators will be fully supported again one day 😄 ).
Edit: After testing again with BS 8.2.0, it seems that the custom operators, even those written in OCaml/ReasonML don't work at all in the new syntax 😞
I'm also interested in this feature. The re-classnames package uses the +
operator (or <:>
if you want to avoid warnings). I am very excited about the new syntax but still need to test if re-classnames works.
@gaku-sei can you shed some light on the functions behind <$> <|> >=>
? What do they represent behind the scenes?
Would it be possible to give some real world examples of their definition and usage?
Hello @IwanKaramazow
Sure 😄
<$>
is basically a equivalent of map
, like List.map
, or Option.map
, so instead of:
Some(42) |> Option.map(x => x + 1)
One can write
open Option // Provided the <$> operator is defined as an alias of map in the Option module
(x => x + 1) <$> Some(42)
It would be actually better using operator + currying, but it seems there is no plan to bring this in the new syntax (even though I don't understand the rational for it: https://github.com/rescript-lang/syntax/issues/105)
(+)(1) <$> Some(42) // return Some(43)
(+)(1) <$> None // return None
// Without operators it could be:
add(1) <$> Some(42)
>>=
is the same as flatMap:
Some("42") >>= float_of_string_opt // return Some(42.0)
None >>= float_of_string_opt // return None
Some("foobar") >>= float_of_string_opt // return None as well
There is also a reversed =<<
operator with its arguments flipped.
The last one <|>
is something that doesn't exist (yet?) in the Belt Api. It's an alias to what could be an "alt" function, taking, let's say, 2 options, and returning the first option that is not None:
Some(1) <|> None // returns Some(1)
Some(1) <|> Some(2) // returns Some(1) as well
None <|> Some(2) // returns Some(2)
None <|> None // returns None
It could obviously be implemented for Results, Arrays, Lists, etc...
Notice that the above operators (and a couple of others, like <<
or <<<
for function composition, <*>
that allows you to apply a function that has been wrapped in an option, a result, a list, etc... or <>
as an equivalent to append
for Arrays, Lists, Strings, etc...) have a well established meaning in most functional programming languages (F#, Haskell, Idris, PureScript, and of course OCaml/ReasonML ^^).
The idea is definitely not to make Rescript a PureScript or Haskell like language, but just to keep the door opened for operators that are commonly used 😕
One can definitely argue that it's still possible to write something like:
// Where x and y have type option(int)
switch (x, y) {
| (Some(x), Some(y)) => x + y
| _ => None
}
But to each their own, and for me it doesn't read as well as:
open Option
add <$> x <*> y
Not because of the keystrokes saved, but because the switch
expression introduces lots of "noises" around the actually interesting piece of code, i.e. x + y
. (Notice that I'm definitely more into "easy to read" than "easy to write" code style, so typing more, if it improves readability is totally ok with me).
As a side note, I have a rather "large" code base written in ReasonML, using operators quite extensively, and this limitation in the new syntax is holding me back from updating to the new syntax (which I actually really love otherwise), since the code now looks like:
\"<$>"(
\"<*>"(x, y))
Which is very hard to read ^^
(Having a way to map operators to functions during the ReasonML -> ReScript reformatting could ease the update though).
I wrote up my thoughts on this in the forum yesterday: https://forum.rescript-lang.org/t/discussion-about-infix-requirements/245
My proposal is implement a restricted form of custom infix operators; require they be surrounded by certain characters for ease of parsing. In my case - and for most of @gaku-sei's examples - using <
and >
to delineate infix operators would work for a great many infix operators. But I'm sure we can swap to different characters if those don't fit within the syntax definition.
Any infix operators that don't fit within this pattern can hopefully be solve with letop, when it eventually arrives (I've had success converting >>=
but I'm not sure that will extend to all use cases).
Hi Everyone!
We have about 15000 loc in ReasonML, and we're looking at our options, sticking to ReasonML, and trying to use Melange, or move to Rescript. We're seeing a heavy community effort in moving towards Rescript, but at the same time, there are some elements in our code-base that make that route a lot harder. The biggest thing is infix operators. There are some we rely on quite heavily internally which are not particularly easy to replace. Take the following example;
let inTime = mapper => P.str("in") *> P.ws *> (Duration.p |> mapper);
It's a parser, that tries to parse a duration prefixed with "in", followed by a whitespace (for instance in 5 seconds
, or in 20 hours
). It's takes a function to map the duration as the duration parser returns an int
and a unit
for the duration. The apply
(*>
) style operator here semantically means, parse the thing on the left hand side, and if it succeeds, parse the thing on the right, but chuck away the thing on the left. So basically, we parse "in"
and some whitespace, and just keep the result of the Duration.p
parser put through the mapper function.
I understand this is quite specific, but coming from a more FP style of programming these "apply" operators are quite familiar. With the upgrade, I'm looking at the following.
open ApplyExtensions;
let inTime1 = mapper =>
applySecond(applySecond(P.str("in"), P.ws), Duration.p |> mapper);
// With a forward pipe, perhaps to make it clearer?
let inTime2 = mapper =>
applySecond(P.str("in"), P.ws) -> applySecond(Duration.p |> mapper);
// Or maybe with a different name entirely?
let inTime3 = mapper =>
keepRight(P.str("in"), P.ws) -> keepRight(Duration.p |> mapper);
The issue for me is that if anything, this makes it harder to onboard new people into our code-base, as this language using apply notation <*>
-- and for instance, bind (>>=
) or choice (<|>
) -- will be extremely familiar to anyone who's ever written a parser in, for instance, Haskell. Also, after refactoring, the pipe-first / pipe-last semantics come into play when I want to apply multiple times. I have a function called applySecond
, or keepRight
, but if I use |>
it's actually the reverse...
A different example:
let pipe = (f, g, x) => g(f(x));
let (>>) = pipe
Right now, we can do things like:
let f1 = fa >> fb >> fc >> fd
In other words, because of the infix operator, we can define a single function (a very simple one at that), and chain them together arbitrary many times. If we remove that operator, and we would like to keep on using our pipe, we would have to start defining a pipe3
and a pipe4
(at least, that's my assumption as there are no variadic arguments). Or perhaps create a heterogenious list type and have pipe take that? But I'm not sure that would make it more clear, and I'm not even sure I could get that to type-check.
Naturally, this could also be done using the forward pipe (like below), and in this simple case that works just fine, but in some cases, especially when doing these things inside for instance a map
or filter
(ie, pushing it into a higher order function), we much prefer the pipe notation and hide the argument (we feel it shows more intent / is more declarative that way).
let f1 = x => fa(x) |> fb |> fc |> fd
My questions is basically two-fold:
- Is support for infix operator coming back? Now, or in the future? Either through overloading a set of default operators, or fully custom ones.
- If the infix operator support isn't coming back - how do you guys migrate the code from Infix to Prefix operators and is there a guideline to make it readable? Just our parser code is about 1500 lines, I feel removing the infix operators would really inflate that. Would love some tips on that front.
I'd just like to close by stating we fully appreciate the huge effort that went into the move from ReasonML to Rescript. Our core product VAST is open-source as well, and we're fully aware what goes into building and maintaining these things. So thanks!
I would be happy to contribute to the effort of bringing some of this functionality back (my personal lens library also relies on infix operators), but this would require a bit of hand-holding as I haven't contributed here before.
P.s. I'm with @TheSpyder in that I think delineating the operators with <
and >
would make perfect sense :)
Linking https://github.com/rescript-lang/syntax/issues/440 here as well just so we can track everything
My team just completed migrating a large (almost 90,000 LOC) project to ReScript. Our biggest hassle with infix functions was >>>
which we used 880 times to compose asynchronous testing steps.
With a lot of fancy footwork and refactoring, we managed to replace it with ->
. This was only possible because our test steps were composable, so we just had to adjust their definition to implement composition in each step instead of having >>>
do it. The end result looks weird now, but I'm sure it will be totally normal in a month or two.
For general infix we just had to get used to it being more verbose. Things like a->flatMap(f)
instead of a >>= f
. That wasn't even hard for us - we make extensive use of labelled arguments, so compositional functions work in either pipe-first or pipe-last. What we ended up with is stuff like
open Option;
a
->map(~f=f1)
->flatMap(~f=f2)
->value(~default=b)
The rescript-lang/syntax repo is obsolete and will be archived soon. If this issue is still relevant, please reopen in the compiler repo (https://github.com/rescript-lang/rescript-compiler) or comment here to ask for it to be moved. Thank you for your contributions.