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

Where does the F# and Smart proposal conflict? Let's split them up into two proposals, one predicate on the other. #134

Closed
babakness opened this issue Aug 28, 2018 · 13 comments

Comments

@babakness
Copy link

Clearly stating if and where these two proposal conflict would go long way. It would seem that the F# is the minimal proposal and that it doesn't conflict with the Smart proposal. So why not make them separate proposals? Let the simpler proposal go through and the Smart enhancement get added. If the value is there, maybe we'll get both at once, otherwise, at lease we'll have some kind of pipeline operator.

Thing is, there A LOT of people looking for this to move forward. In my circles this is like the #1 anticipated feature. Many in the Typescript community is also waiting for this to move forward.

It seems that the hang ups over the specifics of the Smart operator are going to delay this moving forward. Should we use a # character or ? character, etc. How does this play with a general placeholder proposal, etc-- It would be nice to have but not having any pipeline operator is worse than the ergonomics that the Smart operator offers.

@mAAdhaTTah
Copy link
Collaborator

The main concern is that there are currently behaviors that allowed under the F# Pipeline that are currently SyntaxErrors under Smart. This is primarily around the allowance for "bare style" in Smart, which is not as permissive as what F# allows.

The other is that F# is likely to proceed with a solution that doesn't require parentheses around arrow functions in the pipeline, which would be another conflict.

So the two proposals aren't strictly a sub/superset of each other, such that we can't really proceed with F# and land Smart as a follow-on.

@babakness
Copy link
Author

Thanks for the clarification. From a distance it appears that if the F# proposal move forward, another proposal that is Smart-like can still arise; yet the Smart prevents a style some prefer (point-free and currying) where the partial variables are provided, for example

const add = a => b => a + b
const foo = 5
  |> add(5)

Under the Smart plan it would be

const add = a => b => a + b
const foo = 5
  |> add(5)(#)

However, a future Smart-ish proposal can make the above default presumption unless the user provides an explicit #. In other words, presume F# unless Smart-ish syntax is provided. If I am correct, then perhaps this question is valid one to openly ponder: "does the elimination of the presumed function invocation warrant the challenges of coupling these two proposal together?"

One often see this array.map( decorator( fn ) ) and it would seem on-par that passing partially applied curried functions be a thing with pipelines. Also this is common object.method( foo.bind(object) ). A more general solution might be object.method( foo(object, #) ) where foo(object,#) returns a function that takes a single parameter and calls the underlying partially applied function.

Decoupling these proposals, even if they should compete, better proves the stronger proposal. An F# proposal complemented with a general placeholder proposal (arriving at a later time) may or may not prove to be better than the Smart proposal.

Currently, however, the weakest dependency between the tangled proposals becomes parasitic to the other(s).

@mAAdhaTTah
Copy link
Collaborator

All of that is generally true, but you glossed over the "parens around arrow functions" issue. I also suspect that if a Smart pipeline doesn't advance, a solution for placeholders will end up being more general than Smart's implementation. Smart's major benefit / drawback is how much other syntax can be built on top of it in follow-on proposals.

If you fail to introduce placeholders in the initial pipeline proposal, I suspect you won't see Smart advancing as a follow-on.

@babakness
Copy link
Author

Why exactly can't presuming a partially applied curried function (presuming a final (#) call) work with the Smart proposal? I've read through it, it just didn't pop out at me--why not?

@mAAdhaTTah
Copy link
Collaborator

It can; the reason for the restriction is to avoid footguns and reduce ambiguity over the developer intention. Some languages pass the argument to the first position, like Elixir, so this:

x |> f(a)

is desugared to this:

f(x, a)

Requiring a placeholder makes it explicit that it would desugar to this:

f(a)(x)

and not

f(a, x)

as syntactically, you'd need to include the # to specific where that parameter should be passed.

@babakness
Copy link
Author

babakness commented Aug 28, 2018

I agree in that it can be confuse to someone just learning; yet it is a consistent behavior within the language. I've had to explain to beginners what <Component onClick={ myFunction } /> does and where the event is being passed into the function. Once learned however, the student then understands what <Component onClick={ myFunction({ something: 'foobar' }) } /> might do.

As for footguns, the untyped nature of JavaScript enables a plethora of ways to generate runtime errors. What you are suggesting can be address by configuring a linter as needed. Another useful tool is TypeScript; which easily catch these kinds of errors upfront. I personally don't think it is confusing and read code written in this way all the time.

It best to not entangle the progress of the pipeline operator with the considerations the Smart proposal exposes.

@masaeedu
Copy link

masaeedu commented Sep 9, 2018

@mAAdhaTTah Would code like the following (which is valid in the minimal version implemented in Babel) work in the F# proposal?

  const parseCreateVMOutput = s =>
    s
    |> Str.split("\n")
    |> Arr.map(Str.split(":"))
    |> Arr.filter(xs => xs.length === 2)
    |> Arr.foldMap(Obj)(([k, v]) => Obj.embed(k)(Str.trim(v)));

Last time I read through it I wasn't able to suss out exactly which expressions are and are not allowed on the RHS of a |>.

@mAAdhaTTah
Copy link
Collaborator

@masaeedu All of those are valid in F#. None are valid in Smart.

@babakness
Copy link
Author

So what does it take for this to advance? How and who settles what moves forward--smart or F#? Is the next TC39 meeting where this gets hashed out?

The Elixir behavior x |> f(a) becoming f(x, a) is not at all a characteristic of JavaScript. Learning that calling foo(bar) returns a value and that foo(bar)(x) is calling the returned function from foo(bar) with parameter (x) is really JavaScript 101. The basics.

The Smart Operator proposal looks useful because JavaScript lacks a built-in syntax for placeholders. It is of course possible to have a placeholder function right now

const myDivide = placeholder( divide )
pipeline(
  100,
  myDivide( __ , 2)
)

where __ is a object or symbol that the placeholder function intercepts to return a new function. The issue here is syntax, yes this would be nicer

100 
  |> divide( #, 2)

yet this benefit is somehow entangled with a prescription for some perceived ailment in JavaScript--that functions returning functions is confusing. Actually this is even clearer

100 
  |> divideBy( 2 )

Yet not allowed in the Smart Operator as medicine. I disagree here. I like the Smart Operator and would like to see it separated out a benefit JavaScript in general. I dislike the noise of adding # to each and every line. It adds nothing.

100 
  |> divideBy( 2 )( # )
  |> powerOf( 2 )( # )
  |> multiplyBy( -1 )( # )
  // .... and so on with the tail (#) (#) (#)...

In fact it would work just fine with a general placeholder syntax because divideBy( 2 ) returns a function and ( # ) calls that function right away and then wraps it with a placeholder. This is silly; but, maybe some style-guide thinks its safer--which is fine. They can enforce that with a linter.

To prescribe this to all developers as required minute-by-minute, hour-by-hour, daily programming medicine is a misdiagnosis--many don't need or want it. Do away with this mandate or please let us just have F# and move forward. A general placeholder proposal solves what Smart Operator solves anyway.

@mAAdhaTTah
Copy link
Collaborator

So what does it take for this to advance? How and who settles what moves forward--smart or F#? Is the next TC39 meeting where this gets hashed out?

We're currently working on Babel implementations of all 3 proposals, which we'll use to gather real-world feedback from developers to inform our decision.

@igabesz
Copy link

igabesz commented Oct 5, 2018

Here's an even more simplified approach, a very basic implementation, the intersection (or even less) of the 2 current proposals and have it released asap. Man, if we could start using pipes with simple functions on the RHS then it's already a huge win.
#140

@js-choi
Copy link
Collaborator

js-choi commented Dec 7, 2018

For what it’s worth, the trade-offs of the current smart-pipe proposal are not set in stone. They’re fluid and subject to change.

For instance, x |> blah(3) is currently a syntax error because x |> veryVeryVeryVeryVeryLongArbitraryExpressionThatMightOrMightNotContainThePlaceholder is forbidden—and the latter is forbidden because it’s hard to tell whether it contains a placeholder (which would make it a normal expression) or not (which would make it a function call. A disadvantage with the current smart-pipe proposal is wordiness for coding styles that frequently use function-calls-creating-unary-functions, in exchange for preventing human misinterpretation of any long arbitrary RHS expressions (“is it a function call or not?”). This seems to be to be a serious possible mode error that is worth designing against. (Or maybe it isn’t.)

Either way, that’s a fundamental trade-off that might or might not be worth it for different developers in different places and times. x |> blah(3) is forbidden, but this isn’t necessarily the case. x |> blah(3) could be allowed even with smart pipes…in which case the smart-pipe proposal would indeed become a superset of the simple/F#-pipe proposal. It might be worth trying to implement in the Babel plugins as an option.

There’s another trade-off involving non-function expressions (such as await, yield, and arithmetic operators), which are not necessarily equivalent to any possible function call (e.g., it is impossible for yield x to be expressed as any function call). That’s also a disadvantage for styles that frequently use function-calls-creating-unary-functions, in exchange for an advantage for styles that frequently use non-function expressions.

When it comes to trade-offs such as these, all the proposals have opinions, but these opinions are not strongly held. Every decision and pros and cons, nothing is set in stone, and the final result cannot help but be a compromise between fundamental trade-offs…hopefully an optimal compromise for developers in general. The proposals can and should and will be tweaked, based on feedback after the plugins are implemented and the syntaxes actually tried out.

As the author of the current smart-pipe spec, I also want to publicly thank @mAAdhaTTah, who is himself the author of the current simple/F#-pipe spec, for taking the time to reply to questions here about both proposals while I’ve been busy these past months. He is super great. And on behalf of both of us (and everyone else involved), I’d also like to thank everyone here for their patience.

@tabatkins
Copy link
Collaborator

Closing this issue, as the proposal has advanced to stage 2 with Hack-style syntax.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants