So I’m kinda sold on the new ReScript syntax, I feel it’s great for those of us who have been developing in ReasonML and who are coming from JS land.
I found bs-let super handy when I had to build my promise-based (fetch-based) APIs. I couldn’t make it work on ReScript though.
So I just wanted to ask if there’s an official way to have that async/await sugar in ReScript, or do we need to wait on a newer OCaml release to have something like that available in ReScript?
Hey!
There is a statement about async / await on the documentation here:
ReScript’s primary mechanism for async programming is the same as JavaScript’s (callbacks and promises), since we compile cleanly to JavaScript and would like to avoid dragging in a heavy custom runtime.
However, it is planned for us to introduce a coroutine-like feature in the future; for that reason, we’re postponing introducing the keywords
async
andawait
into the language; though our (upcoming) Promise API bindings revamp + pipe will make your async code already look better than otherwise.
Hope this helps!
Edit: The cited text above is outdated, please refer to the up to date promise docs for the latest statement on async / await
Will PPXes be supported by ReScript?
ppxes are still expressable in ReScript, but they are not syntactically optimized iirc. The compiler itself is currently built in a way that makes PPX setups brittle, and they also cause taxing build time overhead when used heavily, especially when you mix different ppxes.
E.g some ppl couldn’t upgrade to the newest compiler bc they were relying on abandoned community ppxes, in some codebases we realized that ppxes take half the time of the build. Some ppl might argue that this is fine, that the advantages of said ppx is worth the build time trade off. Problem with that is that the whole ReScript toolchain heavily relies on the compiler’s build performance speed, so e.g. right now our Editor plugin requires the compiler builds to be in max 200ms range to be reliably responsive when users try to autocomplete, or try to get type hints. It is hard / almost possible to predict the compiler speeds when ppl start extending the syntax in various different ways. Of course we could mitigate build performance issues by caching build results etc, but this is a technical debt we want to push out to the future, until we really really need it.
That’s the reason why we generally advice to not use ppxes, but I do understand why in some use-cases it’s impossible to not use them.
What I’d personally like to see more are meta programming tools that do classical code generation (read in some config file / graphql file / json schema file, emit some ReScript module), than trying to embed their meta programming in the language itself.
This eliminates a lot of problems we have right now: the compiler wouldn’t be slowed down by running external binaries all the time, and the emitted source would be easy to inspect.
For the typical syntactical use cases like async / await and optional access, we will get dedicated syntax at some point, which will heavily improve readibility. It would also allow us to standardize our community on one syntax, instead of telling everyone to use ppx1 for x and ppx2 for y.
That means having a completely separate watcher + build system just for GraphQL and handle all the orchestration of that. And now you are not able to declare queries in your ReScript code files.
The current state of PPX is better than the alternatives for practical purposes. The lack of dynamism in a language like ReScript can be solved with metaprogramming. PPX’s are actually pretty fast, the slow ones are slow because the execution happens via a nodejs script with slow startup times. I agree though that PPX is perhaps not the best implementation, language level macros are a better solution, but they are still in the research phase in OCaml.
ReScript also still runs an ancient implementation of PPX’s, so upgrading that would also help performance.
Totally agree on the macro sentiment. I wonder sometimes if ReScript would be suitable to have its own macro system on a language level. That would guarantee performance and syntax stability as opposed to the mess that writing a ppx is.
I do realise of course, that this would add a huge bag of complexity onto the parser (a debate would need to be had if that is an appropriate thing to do) as well as widen the gap towards Reason.
Still though… Some simple template language like https://github.com/jaredly/reason-macros shouldn’t break the bank. And again: Yes - I realise that achieving the beauty and comfort of the graphql-ppx would be next to impossible with such a simplistic concept.
A risque take is that it might make sense to integrate use-cases that most people need a PPX for into the language. For instance GraphQL. ~40% of people that install ReScript also install graphql-ppx
so having native GraphQL primitives in the language might also be an interesting direction.
We have this now–the ReScript compiler
But being serious for a second, the ReScript team has long recommended checking in generated JS sources. For applications (as opposed to libraries), this is absolutely the right move. I believe the compiler skips re-compiling code that already has up-to-date JS output. So build times should not really be a concern even with PPX, if generated JS is checked in.
Haven’t worked with GraphQL for a while, but a couple of years ago separate .graphql
files meant way better overall DX, especially in WebStorm. So maybe in this particular case, I’d choose separate files over the ppx anyway.
That said… styled-ppx looks attractive. Yes, Tailwind looks attractive too, but maybe that’s too drastic a change. We have a lot of Emotion-based styles here already, and besides, css-in-res has an upside of having one compiler for everything.
Sorry to revive this thread, but I recently ran into bs-let
not working with ReScript and I was wondering what others do when they’d like to do something like async/await
.
I am just using plain promises with rescript-promise. bs-let is problematic in many different ways, and we don’t recommend extending the syntax for upwards compatibility and code robustness reasons.
Just curious, in what different ways are bs-let
problematic?
apparently this is the right timing to ask for this
Got it, okay. I hadn’t seen your library. I’m just starting a new project and was using GitHub - aantron/promise: Light and type-safe binding to JS promises because it seemed popular. I like that your library can interact with plain promises though. Do you have any thoughts about that library in contrast with your own? It would be nice to not have to translate native promises all over my code base.
I tried to explain the rationale of rescript-promise
, with a comparison to aantron/promise
(aka reason-promise
in the PROPOSAL.md document.
Both can interact with Js.Promise.t
types, but rescript-promise
doesn’t add any extra apis to differentiate between rejectable and unrejectable promises. aantron/promise
is a small wrapper around promises and allows rejection tracking of promises within your app. It also fixes an edge case where nested promises cause wrongly reported types in the type system (which we consider rare in normal use).
This has ups- and downsides in ergonomics though, especially if you are doing a lot of interop. The rescript-promise
apis are also more familiar for JS devs, and will later be part of the core bindings that are shipped with the compiler.
To summarize: If you can deal with the extra complexity, and wish to track every single rejectable promise in your codebase, you can try aantron/promise
. For the light version “that just works like in JS” with a little less guarantees, use rescript-promise
.
Thank you for the reply! I didn’t realize you compared them in your proposal.
bs-let
works. But in rescript syntax the ppx syntax nests the calls anyway (so no use in using it).
For example (probably not the best example, but from a random file in my project):
Reason Syntax
let%AwaitThen _ =
HelperActions.mintDirect(
~marketIndex,
~amount=initialAmountShort,
~token=paymentToken,
~user=testUser,
~longShort,
~oracleManagerMock=oracleManager,
~isLong=false,
);
let pricesBelow = Belt.Array.makeBy(numberOfItems - 1, i => i);
let%AwaitThen (_, resultsBelow) =
pricesBelow->Array.reduce(
(initialPrice, [||])->JsPromise.resolve,
(lastPromise, _) => {
let%AwaitThen (lastPrice, results) = lastPromise;
let newPrice = lastPrice->sub(CONSTANTS.tenToThe18);
let%AwaitThen _ =
oracleManager->OracleManagerMock.setPrice(~newPrice);
let%AwaitThen _ =
longShort->LongShort.updateSystemState(~marketIndex);
let%AwaitThen shortValue =
longShort->LongShort.syntheticTokenPoolValue(
marketIndex,
false/*short*/,
);
let%AwaitThen longValue =
longShort->LongShort.syntheticTokenPoolValue(
marketIndex,
true/*long*/,
);
(
newPrice,
[|
(
newPrice->Ethers.Utils.formatEther,
shortValue->Ethers.Utils.formatEther,
longValue->Ethers.Utils.formatEther,
),
|]
->Array.concat(results),
)
->JsPromise.resolve;
},
);
prices :=
prices.contents
->Array.concat(resultsBelow)
->Array.concat([|
(
initialPrice->Ethers.Utils.formatEther,
initialAmountShort->Ethers.Utils.formatEther,
initialAmountLong->Ethers.Utils.formatEther,
),
|]);
()->JsPromise.resolve;
Turns into the following rescript:
%AwaitThen({
let _ = HelperActions.mintDirect(
~marketIndex,
~amount=initialAmountShort,
~token=paymentToken,
~user=testUser,
~longShort,
~oracleManagerMock=oracleManager,
~isLong=false,
)
let pricesBelow = Belt.Array.makeBy(numberOfItems - 1, i => i)
%AwaitThen({
let (_, resultsBelow) = pricesBelow->Array.reduce((initialPrice, [])->JsPromise.resolve, (
lastPromise,
_,
) =>
%AwaitThen({
let (lastPrice, results) = lastPromise
let newPrice = lastPrice->sub(CONSTANTS.tenToThe18)
%AwaitThen({
let _ = oracleManager->OracleManagerMock.setPrice(~newPrice)
%AwaitThen({
let _ = longShort->LongShort.updateSystemState(~marketIndex)
%AwaitThen({
let shortValue =
longShort->LongShort.syntheticTokenPoolValue(marketIndex, false /* short */)
%AwaitThen({
let longValue =
longShort->LongShort.syntheticTokenPoolValue(marketIndex, true /* long */)
(
newPrice,
[
(
newPrice->Ethers.Utils.formatEther,
shortValue->Ethers.Utils.formatEther,
longValue->Ethers.Utils.formatEther,
),
]->Array.concat(results),
)->JsPromise.resolve
})
})
})
})
})
)
prices :=
prices.contents
->Array.concat(resultsBelow)
->Array.concat([
(
initialPrice->Ethers.Utils.formatEther,
initialAmountShort->Ethers.Utils.formatEther,
initialAmountLong->Ethers.Utils.formatEther,
),
])
()->JsPromise.resolve
})
})