Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement monadic syntax sugar #1326

Open
bobzhang opened this issue Mar 7, 2017 · 10 comments
Open

implement monadic syntax sugar #1326

bobzhang opened this issue Mar 7, 2017 · 10 comments

Comments

@bobzhang
Copy link
Member

@bobzhang bobzhang commented Mar 7, 2017

see discussions in #1214

after some research, below is the syntax I think will be great and extensible in the future

let [@bs] v0  = promise0 in  (* default to Promise.bind *)
let [@bs] v1 = promise1 in 
let [@bs Option] v2 = optionl2 in  (* now default to Option.bind *)
let [@bs ] v3 = optional3 in 
let [@bs Promise] v4 = promise4 in (* back to promise *)
let [@bs error] v5 = error_handling in  (* here bind to Promise.catch *)
...  

We chose attributes instead of extension (like janestreet let%bind) because attributes allow payload for more customization later

todo:

research on semantics of let .. and

@yawaramin
Copy link
Contributor

@yawaramin yawaramin commented Apr 7, 2019

Bob, can we do something like what dune is doing and backport the new let* syntax? From that post:

The shim preprocessor converts bindings operators to OCaml identifiers of the form let__XXX and and__XXX. For instance, let+* is translated to let__plus_star. So you must make sure to not use such identifiers in your code.

I imagine this would be something like let$plus$star in BuckleScript output JS? We could have this shim in bsppx and get rid of it once BuckleScript lands 4.08+.

Loading

@utkarshkukreti
Copy link
Contributor

@utkarshkukreti utkarshkukreti commented Jun 15, 2019

OCaml 4.08.0 is out. Would be really cool to have what @yawaramin suggested now!

Loading

@bobzhang
Copy link
Member Author

@bobzhang bobzhang commented Jun 21, 2019

There are some nice features in 4.08, so we may target 4.08.1 sooner than original timeline, we will see in next few months

Loading

@mrmurphy
Copy link

@mrmurphy mrmurphy commented Aug 23, 2019

@bobzhang, in order to be able to actually use let*/let+ for async binding, what are all the things that would have to happen? I was trying to dig into it a while back and this is what I understood from asking around:

  • We'd have to have a version of BSB that targets 4.08.1.
  • Refmt would have to parse let*/let+.
  • Bucklescript would have to recognize the language construct (or AST node?) and do the transformation of turning the code on the lines following the let into a callback.

Is that roughly correct? I apologize if my terminology is all wrong. I'm not very experienced in language development. But since you folks are busy making with other cool language features, I thought maybe I could help organize thoughts or efforts around this feature, and take some pressure off of you.

I think this bind sugar really matters to the growth of the community, especially those Javascript developers who are used to async/await and thinking of coming to Reason. I personally have found that I end up reaching for https://github.com/jaredly/let-anything in pretty much every new project that I start. It can drastically improve the readability of my code.

Loading

@mrmurphy
Copy link

@mrmurphy mrmurphy commented Aug 23, 2019

According to @anmonteiro this is what needs to happen before we can have a feature like this included in the language:

  1. refmt would need to upgrade to the 4.08 AST (it's currently on the 4.04 AST - I have a PR upgrading it to the 4.06 AST but it's more than a year old...)
  2. BuckleScript would need to upgrade to the 4.08 compiler if people using BuckleScript would like to take advantage of this syntax.

Is that in line with your thoughts @bobzhang?

Loading

@aantron
Copy link

@aantron aantron commented Aug 23, 2019

On the native side, Dune has future_syntax (https://dune.readthedocs.io/en/stable/dune-files.html#future-syntax) to allow older compilers to use let+. That preprocessor could also be adapted. But it's probably "smarter" to switch to 4.08.

Loading

@nadav-lavy-exa
Copy link

@nadav-lavy-exa nadav-lavy-exa commented Feb 3, 2020

Has there been any progress on this front?

Loading

@yawaramin
Copy link
Contributor

@yawaramin yawaramin commented Feb 3, 2020

Loading

@shubhamgupta731
Copy link

@shubhamgupta731 shubhamgupta731 commented Mar 29, 2020

Is this now available as part of bs-platform?

Loading

@leeor
Copy link

@leeor leeor commented Sep 8, 2020

Wanted to add my $0.02 on this, after the repackaging of ppx_let for rescript, working with bs-let for many months, and recently also using Reason's let operators through reason-repacked.

Right now, the following Reason code which uses a let extension:

let fn = () => {
  let%bind p = Js.Promise.resolve(42);
  let%bind x = Js.Promise.resolve(p * 2);

  Js.Promise.resolve(x + 42);
};

Is converted to:

let fn = () =>
  %bind(
    {
      let p = Js.Promise.resolve(42)
      %bind(
        {
          let x = Js.Promise.resolve(p * 2)

          Js.Promise.resolve(x + 42)
        }
      )
    }
  )

The original suggestion is to make it look like this:

let fn = () => {
  let [@bs] p = Js.Promise.resolve(42)
  let [@bs] x = Js.Promise.resolve(p * 2)

  Js.Promise.resolve(x + 42)
}

Or, if I use a module that provides an abstraction over promises for long running async operations:

let fn = () => {
  let [@bs AsyncOp] p = AsyncOp.execQuery(42)

  // then option 1
  let [@bs AsyncOp] x = AsyncOp.return(p * 2)
  AsyncOp.return(p * 2)

  // Or option 2
  let [@bs AsyncOp.map] x = p * 2
  (x + 42)
}

My thoughts:

  1. Correct me if I'm wrong, but no existing PPX that provides let binding (bs-let, ppx_let) will work out-of-the-box.
  2. I think there shouldn't be a default. It is obscure and uninformative, and Context is King. What's more, there are many async libraries out there, catering to different flavors, so the upside of having a default might end up being negligible.
  3. The error case is unclear.
  4. I like the idea of being able to support richer expressiveness through additional payloads.
  5. I think a let extension (a-la let%bind) is concise and meaningful with less typing than an attribute, esp. when the attribute requires at least 4 characters (3 keystrokes) to begin with. With attributes, using a non-default module will require 4 keystrokes + the module name. When combined with modules like in ppx_let, you get a very robust system.
  6. Maybe a better, more DX-friendly approach, would be to start with a let extension, then when there is a need for a richer payload an attribute can be added. If there's a need for such a payload today, this approach could still provide a better DX. The problem with attributes is the typing overhead.
  7. As for the specific semantics, I think that the approach suggested by Jane Street in their ppx_let docs is very practical and has good DX. You define a module with the relevant operators, then you can open it for the let-operators to work in that context/space.

An example of the last point:

module AsyncOpSpace = {
  let bind = AsyncOp.bind
  let map = AsyncOp.map
  let return = AsyncOp.return
}

let fn = () => {
  open AsyncOpSpace // this provides context

  let.bind p = AsyncOp.execQuery(42)
  let.map x = p * 2

  (x + 42)
}

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
8 participants