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

Cleanup fast pipe implementation #2259

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

IwanKaramazow
Copy link
Contributor

@IwanKaramazow IwanKaramazow commented Nov 11, 2018

-> should just be |., a regular infix function with interesting printing/precedence properties as if it was a ., and then ppx does all the real logic. Function application on the right side of fast pipe is a must if you want the regular infix behaviour. The following expressions should be all valid without requiring weird parens:

e->ReactEvent.Form.target##value
a->f(c)->Js.log;
a->f(~named, ~bar)->g(~z, ())->Js.log

The current implementation is based on the fact that it needs to be easy to stub:
let (|.) = (a, f) => f(a);
This results into the rhs of -> not taking function application to make this work:
a->f(b) is parsed as (a->f)(b).
The fact that there isn't function application on the rhs, has resulted into a wide range of problems with fastpipe. In printing you actually want the layout to break as if there was a function application on the rhs! The printing problems were fixed at the cost of a tremendous amount of complexity in the printer.

The following question springs to mind: are the semantics (evaluation order etc) identical when defined in user space like this, as opposed to ppx?

No, they aren't. Example:

let fn1 = (~foo=?, ()) => 1;
let fn2 = (~bar=?, x) => 2;

/* Ok */
fn1(~foo=1, ())->(fn2(~bar=2))

/* Ok */
fn1(~foo=1,()) |. fn2(~bar=2)

/* Error: This expression has type int. It is not a function. */
fn1(~foo=1, ())->fn2(~bar=2)

/* Explanation */
fn1(~foo=1, ())->fn2(~bar=2)

/* this translates to: */
fn2(1)(~bar=2)

/* however, because `~bar` is optional, `fn2(1)` already produced a value. Therefore: */
2(~bar=2)

In retrospect, not having function application on the rhs was a terrible mistake.
Fast pipe is all about function application in its current form.
If it was always done via ppx, then you wouldn't get the error in the example
above. Having -> behave as regular infix operator with better precedence, also strokes better
with the mental model of |. for function/variant piping.

TODO:

  • improve printing of function application with single fast pipe
  • add extra tests
  • take care of a ppx for rtop/native usage

Fast pipe desugars to |. Why? It's easy stub-able like: let (|.) = (a, f) => f(a);
This results into the rhs of -> not taking function application to make this work:
a->f(b) is parsed as (a->f)(b).
The fact that there isn't function application on the rhs, has resulted
into a wide range of problems with fastpipe.
In printing you actually want the layout to break as if
there was a function application on the rhs! The printing problems were
fixed at the cost of a tremendous amount of complexity in the printer.

Are the semantics (evaluation order etc) identical when defined in user space like this, as opposed to ppx?

No, they aren't…

```
let fn1 = (~foo=?, ()) => 1;
let fn2 = (~bar=?, x) => 2;

/* Ok */
fn1(~foo=1, ())->(fn2(~bar=2))

/* Ok */
fn1(~foo=1,()) |. fn2(~bar=2)

/* Error: This expression has type int. It is not a function. */
fn1(~foo=1, ())->fn2(~bar=2)

/* Explanation */
fn1(~foo=1, ())->fn2(~bar=2)

/* this translates to: */
fn2(1)(~bar=2)

/* however, because `~bar` is optional, `fn2(1)` already produced a value. Therefore: */
2(~bar=2)
```

In retrospect, not having function application on the rhs was a terrible mistake.
Fast pipe is all about function application in its current form.
If it was done via ppx, then you wouldn't get the error in the example
above.
@jordwalke
Copy link
Member

I'm really glad you are cleaning up the code. This is how we can keep the printer sustainable and move towards a better long term printing framework (by making the code as simple as we can).

Let's make sure we also include in this PR, a summary of how current users of fast-pipe in BS will be impacted by this (what will be necessary to mitigate that impact).

@jaredly
Copy link
Contributor

jaredly commented Nov 13, 2018

I'm confused at what this changes. Could you clarify in the description, what the transformation ends up being?

I expect this to transform to

e->ReactEvent.Form.target##value
a->f(c)->Js.log;
a->f(~named, ~bar)->g(~z, ())->Js.log

this

ReactEvent.Form.target(e)##value
Js.log(f(a, c));
Js.log(g(f(a, ~named, ~bar), ~z, ()))

Is that still the case?

@IwanKaramazow
Copy link
Contributor Author

It will still be the case. The current behaviour is actually different to provide easy stubs on native:
let (|.) = (a, f) => f(a).

ReactEvent.Form.target(e)##value
Js.log(f(a)(c));
Js.log(g(f(a)(~named, ~bar))(~z, ()))

@jaredly
Copy link
Contributor

jaredly commented Nov 13, 2018

Ah ok, that makes sense :) Sounds great!

@sikanhe
Copy link
Contributor

sikanhe commented Jan 7, 2020

What is the status on this PR? I have stumbled a couple times when using fastPipe with labeled argument result in weird type errors that require me to wrap the rhs of fast pipe with parens

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

Successfully merging this pull request may close these issues.

5 participants