Rationale for Bringing Promises to Reason


#1

What’s the rationale for bringing Promises to Reason? It seems shortsighted to force a buggy construct from JS-land into Reason. Will Reason promises address the issues listed here?

tl;dr:

  1. Promises can’t be cancelled
  2. Promises are eager
  3. Users aren’t forced to deal with errors
  4. Exceptions are mixed with failures

If these problems won’t be fixed in Reason’s version of promises, why not? And if they are, then maybe we should call them by a different name, since their behavior will differ significantly from their JS counterparts.


#2

Currently, the Js.Promise is purely binding for JS’s promises. It’s unsound and everyone is aware of that. There are several attemps for creating a sound and fully typed promise api in Reason. For example:


#3

Cool, is this what the dedicated async/await syntax would be based off of? That’s the part I’m worried about the most.


#4

the issues you point out with js promises are real, but your questions rest on a few questionable premises:

  1. that promises are “being brought to” Reason. They’re not: Promises are already here, as a pervasive part of the JS landscape with which Reason is designed to interoperate.
  2. that there is such a thing as “Reason promises”. There aren’t: the stdlib just provides a typed interface for javascript promises, and does not make any of the implementation decisions that you call out here.
  3. that anyone on the ReasonML team is forcing you to use Promises. They aren’t: if you find the construct inappropriate for your use case, you are free to use something else, the same way you are not forced to use a linked list.

If your concern is specifically about syntax blessing this construct you dislike, that’s legit—you might want to read up on the GitHub issue and other resources that it links to. From the looks of it, everyone wants to be able to support both promises (because, again, they are an important part of the landscape) and more principled abstractions like the OCaml LWT library—there’s just some back-and-forth on syntax, extensibility, and so on.


#5

With being able to implement something elegant like Callbags do we need promises? Especially if we implement a fully reactive paradigm in our programs?

Seems like await-async and promises are redundant for most of what we would do if we implement observables and iterables like this:
https://staltz.com/why-we-need-callbags.html

Here is a Reason implementation of the Callbag specification:

And a Reason implementation of xstream, called reason-xstream:

Thinking in reactive is a natural fit for functional programmers:

In the past I’ve helped write parts of RxJS, I’ve written xstream, and now I bring a new stream library for JavaScript. This time, it’s a bit different, though, because there are 3 new things:
A specification for callback-based programming_
A reactive stream programming library_
An iterable programming library_

I’ve come to Reason because I want to train my brain to think in the three primary paradigms of declarative, functional and reactive programming and apply them to React.

Do we need promises for that? I think Callbags shows us that we can use an elegant abstraction that doesn’t obscure the base primative: the best kind of abstraction.

A talk about stream IO:


#6

Thanks for the clarification @Riwsky! That makes perfect sense, and the rich interop is what drew me to ReasonML in the first place.

wrt point 3, I never assumed the ReasonML team was forcing me to use Promises, just that if they were the de-facto async primitive provided by the stl, they would easily become just as pervasive in the Reason community as they are in the JavaScript community. In that case, I would be forced to at least deal with them as many libraries would return/expect them.

So yes, my concern is about this on the promises docs page.

Thanks again!


#7

@slavsquatch: as mentioned by @Riwsky, one of the main reasons for having promises in Reason is interop.

However, something often overlooked is that direct interop with JS promises doesn’t mean we have to make the same mistakes that JS made. Using the type system, we can force JS promises to be sound, force explicit error handling, and separate exceptions from other errors. That addresses soundness and points (3) and (4). See Repromise for an example implementation that does this:

On BuckleScript, Repromise is implemented, internally, directly as JS promises for full interop. It also has a pure-Reason implementation, that can be used on native and with BuckleScript. The pure-Reason implementation inherently doesn’t need to make any tradeoffs for JS interop :slight_smile:


Cancelation is pretty tricky to get right, once one starts considering diverse use cases. But, if we have a good design for it, we can implement cancelation, whether on top of JS promises, or in pure Reason. The same goes for lazy promises.


Concerning naming, the concept of promises isn’t limited to only JS, and we shouldn’t assume that the buggy JS implementation is even a good example of promises. I say we keep the name. But if this is going to be a stumbling block for learning, we can switch to a synonym like futures or deferreds. IMO, if we are going to have interop with promises, we should call the Reason side of it promises as well, even if the API is a bit different.

In the end, promise/future/deferred are all names that are used by various languages and libraries for what they chose to implement from the same promise/future/deferred design space. By comparison, lots of languages have various kinds of maps in their libraries. We still refer to most of them as maps, even while keeping their important differences in mind.


#8

(Because I’m limited to two links per post…)

There’s a discussion of how errors and exceptions should be handled here: https://github.com/aantron/repromise/issues/16. What Repromise currently does for soundness is discussed here: https://github.com/aantron/repromise/issues/8.