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

What's keeping this from Stage 1? #24

Closed
5 of 6 tasks
dead-claudia opened this issue Sep 17, 2015 · 78 comments
Closed
5 of 6 tasks

What's keeping this from Stage 1? #24

dead-claudia opened this issue Sep 17, 2015 · 78 comments

Comments

@dead-claudia
Copy link
Contributor

dead-claudia commented Sep 17, 2015

Edit: Keeping this list up to date.

TL;DR: See title.

This already has transpiler support, and has definitely fulfilled at least most of the requirements for Stage 1. These are the ones I can tell are very clearly, unambiguously met (pulled from the process document):

  • Identified “champion” who will advance the addition
  • Prose outlining the problem or need and the general shape of a solution
  • Illustrative examples of usage
  • High-level API
  • Discussion of key algorithms, abstractions and semantics
  • Identification of potential “cross-cutting” concerns and implementation challenges/complexity

I'm just wondering what's holding this back from Stage 1.

@zenparsing
Copy link
Member

I'm the champion. : )

There are a couple of reasons that I haven't attempted to advance this proposal with TC39:

First, when I presented this proposal back in March, I received a fair amount of pushback on the following grounds:

  • This proposal "abuses" this to support functional programming. ES6 made progress in "taming" the dynamic (and confusing) nature of this in JS, and the bind proposal would actually make things more confusing.
  • Using the :: operator for "virtual methods" would confuse programmers about property access in general.
  • The prefix :: is just kinda weird looking.

On balance, TC39 wasn't particularly supportive. We went ahead with getting it into Babel, though, in order to gather some feedback. That feedback has been really useful.

I believe that the next step is to decide whether we would be better off splitting the proposal into separate "method extraction" and "functional pipelining" proposals. I'm cautiously in favor of that.

The second big reason that I haven't advanced this proposal is that I don't think this is the right time for adding syntax to JS, with the big exception of async functions. I think we need to focus on just async functions for ES7/2016/whatever.

Too much syntax too quickly isn't a good thing, especially given the huge amount of syntax added with ES6.

@dead-claudia
Copy link
Contributor Author

Okay. Understandable.

@shovon
Copy link

shovon commented Sep 18, 2015

I'm curious about functional pipelining. How would that work out?

@dead-claudia
Copy link
Contributor Author

dead-claudia commented Sep 18, 2015

Edit (Aug. 12, 2016): This is merely a strawman proposing a change to the proposal, not the actual proposal itself. Just to clarify.


@zenparsing I do like the idea of separating these two ideas.

First of all, method binding a la Java would look really nice. It could even open up the possibility of a property shorthand.

getBooks().then(JSON.parse).then(books => books.map(book => new Book(book)))

// Simplify constructor call
getBooks().then(JSON.parse).then(books => books.map(new::Book))

// Shorthand binding of book => book.length
books
  .then(books => books.filter(compose(::length, atLeast(5))))
  .then(books => books.map(new::Book))

Shorthand for book => book[prop]
books.then(books => books.map(::[prop]))

It would be incredibly convenient, and it is also very commonly used. It's also extremely optimizable.

As for a pipelining syntax, I was kinda thinking of something like this:

function *map(f) {
  for (const i of this) yield f(i)
}

function* concat() {
  for (const i of this) {
    if (i[Symbol.iterator]) yield* i
    else yield i
  }
}

function *flatten() {
  yield* this
    ->map(x => x->flatten())
    ->concat()
}

@dead-claudia dead-claudia reopened this Sep 18, 2015
@dead-claudia
Copy link
Contributor Author

And for the dynamic this problem, it's already come up in TypeScript land because JS already calls methods by delegation (prototypal vs classical), and there's no way to properly type Function.prototype.bind and friends without it.

class A {
  foo() { return "A!" }
  bar() { console.log(this.foo()) }
}

class B {
  foo() { return "B!" }
}

console.log(new B().bar()) // "B!"

@ssube
Copy link

ssube commented Sep 18, 2015

@IMPinball I think the method being bound should be on the right of the operator, for consistency both internally and with other languages. Type::new or Type::static and instance::method provide a predictable way to reference a call of some sort, even though new is an operator (putting it before the type seems like you're inverting the application). All forms can desugar into simple arrow functions or even just function calls.

@dead-claudia
Copy link
Contributor Author

@ssube Then how would you do something like Base.new.bind(Base)? It's not an unheard of name, and it could be the right name for some data types.

@ssube
Copy link

ssube commented Sep 18, 2015

@IMPinball I think the property access should take care of that: instance::[new]. I'm not sure I've seen that in use, but explicitly marking it as the new property should work.

@dead-claudia
Copy link
Contributor Author

@ssube I like that idea.

On Thu, Sep 17, 2015, 23:57 Sean Sube [email protected] wrote:

@IMPinball https://github.com/impinball I think the property access
should take care of that: instance::[new]. I'm not sure I've seen that in
use, but explicitly marking it as the new property should work.


Reply to this email directly or view it on GitHub
#24 (comment)
.

@zenparsing
Copy link
Member

@shovon A pipelining operator (found in some languages like F# as |>) would address the "iterlib" use case described in this proposal. It would look like:

function fn(a, b) {
    console.log([a, b].join(", "));
}

"hello"->fn("world");
// > "hello, world"

Where the left hand side is used as the first arg in call to the function on the right hand side.

@dead-claudia
Copy link
Contributor Author

Closing in favor of #26, as the initial questions have been answered.

@deontologician
Copy link

It seems like this issue isn't really resolved since a bunch of the community want to see it move forward, and it still isn't. Is it possible to reopen this so people have a place to discuss the fact that it's being blocked? (re: #30 (comment)) Right now #30 has the most recent discussion but isn't actually about the progress of the proposal

@zenparsing
Copy link
Member

@deontologician Happy to reopen.

I would be willing to present this proposal to the committee again, but would like a co-champion.

@zenparsing zenparsing reopened this Jun 14, 2016
@deontologician
Copy link

Does the co-champion have to be on the committee already?

@zenparsing
Copy link
Member

@deontologician Yes.

@deontologician
Copy link

Is there a list of people on TC39? I found http://tc39wiki.calculist.org/about/people/ but I'm not sure if it's up to date

@donaldpipowitch
Copy link

I really hope this proposal will be presented again to TC39. It just too useful for things like the 'iterlib' example, adding custom operators to observables without modifying the prototype (e.g. this ideal world) and so.

I just wrote a couple of helper functions to make selenium tests really easy to write:

// gets the third user, opens menu, clicks delete
await '.users'::eq(2)::get('.menu')::click()::get('.delete')::click();

Every helper function is async/await and awaits the previous this, if it is a Promise. This is super hard to write and use without ::.

@RangerMauve
Copy link

@donaldpipowitch You could just call .then() and have those functions return callbacks. :P

@robotlolita
Copy link

@ljharb

I would be willing to present this proposal to the committee again, but would like a co-champion.

@robotlolita
Copy link

@flying-sheep my point is more that foo does not influence the meaning of bar (bar is resolved in the static scope, regardless of what foo means). The distinctive feature of method calls is that the operation (bar) is selected based on one or more special arguments (foo/this being the usual one).

So in that case it's just a regular, infix function call, with syntax that looks like something entirely different (namespaces in Ruby, bound methods in Java, etc). Purr had a similar problem where a foo: bar looked like Smalltalk's a foo: bar method call (a.foo(bar)), but really meant the same as JS's foo(a, bar)).

@flying-sheep
Copy link

of course you’re right that there’s no functional difference here. both are just ways to call freestanding functions.

so your problem with :: is that in your eyes, that intuitive way to interpret :: is misleading?

@gajus
Copy link

gajus commented Feb 20, 2017

I have been overloading the :: operator for quite sometime now and I love how it helps me to structure the code mimicking Ramda-esque pipe operator.

Please see https://github.com/gajus/babel-plugin-transform-function-composition.

For a visual example:

Input:

apple
  ::foo('foo parameter 0', 'foo parameter 1')
  ::bar('bar parameter 0')
  ::baz('baz parameter 0');

Output:

baz(
  'baz parameter 0',
  bar(
    'bar parameter 0',
    foo(
      'foo parameter 0',
      'foo parameter 1',
      apple
    )
  )
);

Unlike the bind proposal, this implementation does not concern with the invocation context. babel-plugin-transform-function-composition uses the :: operator to create a partially applied function such that the left hand side of the operator is set as the the first parameter to the target function on the right hand side.

I know that this is unlikely to be serious contender to the current proposal because of the JavaScript-land conventions, but throwing it into the conversation for others awareness.

BIG WARNING: This is a proof-of-concept. Do not use it in production. See Motivation.

@dead-claudia
Copy link
Contributor Author

@gajus Interesting!

@flying-sheep
Copy link

flying-sheep commented Feb 21, 2017

OK, so there’s now three slightly different proposals:

given the operator and the expression LHS ⌷ RHS, or LHS ⌷ RHSfunction(...RHSargs)

  1. bind operator (this one. works as syntax sugar for RHSfunction.call(LHS, ...RHSargs))

    foo::bar()     bar.call(foo)
    foo::bar(baz)  bar.call(foo, baz)

    Pro: Arguments in untransformed expressions match arguments in transformed one, only this changes. Nice intuition (use a function as if it was a bound method)
    Con: Only works with function, not =>

  2. curry operator (@gajus’ one. syntax sugar for RHSfunction(LHS, ...RHSargs))

    foo::bar()     bar(foo)
    foo::bar(baz)  bar(foo, baz)

    Pro: Familiar for *ML veterans. Clean syntax before and after transform
    Con: Visually changes the number of parameters a function is called with, therefore the most confusing approach for people with no *ML background

  3. pipeline operator (that one. syntax sugar for RHS(LHS))

    foo |> bar               bar(foo)
    foo |> _ => bar(_, baz)  bar(foo, baz)

    Pro: Simple and clean with existing one-arg functions. Explicit.
    Con: More verbose when supplying more than one argument (need to create a new function)

@bathos
Copy link

bathos commented Feb 21, 2017

@flying-sheep I believe the gajus model is RHSfunction(...RHSargs, LHS) rather than RHSfunction(LHS, ...RHSargs).

@gajus can you explain more about why that is?

@flying-sheep
Copy link

oh right. i didn’t read properly and just assumed it would be that way.

@gajus
Copy link

gajus commented Feb 21, 2017

@flying-sheep I believe the gajus model is RHSfunction(...RHSargs, LHS) rather than RHSfunction(LHS, ...RHSargs).

@gajus can you explain more about why that is?

I cannot recall the exact reason, though I am pretty sure it was done to be consistent with Ramda.

From Ramda documentation:

  • Ramda functions are automatically curried. This allows you to easily build up new functions from old ones simply by not supplying the final parameters.
  • The parameters to Ramda functions are arranged to make it convenient for currying. The data to be operated on is generally supplied last.

The parameters to Ramda functions are arranged to make it convenient for currying. The data to be operated on is generally supplied last.

– http://ramdajs.com/

Ramda core contributors (ping @buzzdecafe @davidchambers @CrossEye @megawac @ndhoule) are in better position to argument this design decision.

@dead-claudia
Copy link
Contributor Author

dead-claudia commented Feb 21, 2017 via email

@ljharb
Copy link
Member

ljharb commented Feb 21, 2017

I think perhaps talking about alternative proposals on this proposal's repo isn't particularly productive?

It seems that the only thing this issue needs is to identify a champion who is already on TC39. Discussion of anything else, prior to that, is premature.

I'd be happy to do it - I'm a very big fan of the original proposal - but I won't have the time to take it on for quite awhile.

@dead-claudia
Copy link
Contributor Author

@ljharb I agree, and in hind sight, I probably should've not replied with that... I did take one of the cons and converted it into a new suggestion (see #46), as it looks like it should already be that way, though.

  • Reflect.apply, Function.prototype.call, Function.prototype.apply, and Function.prototype.bind can be implemented in terms of this, and the common %Call native no longer needs to exist in V8 and friends. So all of those can then be implemented at the language level. Implementors would appreciate this.

Note that this pro I listed is false, because changing Array.prototype[Symbol.iterator] breaks such code. I only realized this after I sent it... 😦

@ljharb
Copy link
Member

ljharb commented Feb 22, 2017

using :: instead of .call doesn't rely on Symbol.iterator - so they wouldn't need %Call i'd think?

@dead-claudia
Copy link
Contributor Author

dead-claudia commented Feb 22, 2017

@ljharb The spread operator in foo(...args) calls args[Symbol.iterator]() unconditionally, which is why.

@ljharb
Copy link
Member

ljharb commented Feb 22, 2017

right but a::b(c, d) doesn't use that

@dead-claudia
Copy link
Contributor Author

@ljharb I'm aware. I was specifically referring to those specific methods, which require variadic application.

@MeirionHughes
Copy link

MeirionHughes commented Aug 31, 2017

How about people contacting/pinging champions to get feedback as to why they don't want to be champion? Might be worthwhile finding out what needs to change (i.e. drop the ::foo case?) before they'd advocate.

@zenparsing its been 2 years since your remarks - all the async changes have filtered through now, so perhaps now things are relatively quiet to think about this again? Would you want split off the "functional pipelining" proposal before picking it up again?

@dead-claudia
Copy link
Contributor Author

@MeirionHughes In all honesty, this strawman in its current form is for all intents and purposes dead, and most of the conversation here is already looking for better ways to handle this (as the current strawman has quite a few shortcomings).

@flying-sheep
Copy link

flying-sheep commented Aug 31, 2017

i don’t share this view. i think the tradeoffs here are preferable to the ones in other approaches.

i’ll refer to the three options as “bind-style” (bar.call(foo, baz)), “partial-style” (bar(foo, baz)) and “curry-style” (bar(baz)(foo))

e.g. you say the bind-style “Doesn't work well with libraries like Lodash”, but neither does curry-style. people had to write lodash-fp, and such a port is easy if you already have a final API you just need to adapt in a rather mechanic way.

also you say bind-style was “OO”, but it isn’t: “OO” means “data coupled with behavior”, which this proposal decidedly isn’t: 5::add(1) has completely functionally decoupled data and behavior.

finally you mix syntax and semantics. partial-style doesn’t need to use ::, it could just as well have -> or |> as operator.

@dead-claudia
Copy link
Contributor Author

@flying-sheep To clarify, here's some of its shortcomings (and why we need a better proposal):

  1. this isn't easily aliased - Explicit naming of this #30
  2. ::foo.bar !== ::foo.bar - Make binding (mostly) idempotent #46
  3. People expect ::object.foo to be object::foo - ::object.method vs object::function #18, Ambigousness #43, The unary form feels like it's really a binary operator in disguise. #8
  4. I'm not the only one finding the use of this to be awkward - Why bind to this and not pass as an argument? #31
  5. The common case is just calling the function, hence why I filed Drop the this binding altogether #44

Before I proceed further, we should really be talking about this (beyond the binding side) over in this repo.


e.g. you say the bind-style “Doesn't work well with libraries like Lodash”, but neither does curry-style.

I personally proposed foo->bar(baz), equivalent to bar(foo, baz), which is functional, but not curried. I also noted in depth here the pros and cons of each possible pipeline desugaring (foo.call(bar, ...), foo(bar, ...), and foo(...)(bar) - note that I used :: instead of -> when explaining the partial-style one).

such a port is easy if you already have a final API you just need to adapt in a rather mechanic way.

I'm aware, but it is something to consider. In particular, it's why this-based chaining

also you say bind-style was “OO”, but it isn’t: “OO” means “data coupled with behavior”, which this proposal decidedly isn’t: 5::add(1) has completely functionally decoupled data and behavior.

I mean OO in the sense that:

  1. It's defined to use the language's primary idiom for object context: this.
  2. It is invoked very similarly to a method, and is somewhat awkward to invoke any other way.
  3. When you reference it like a method without calling it, it binds itself just like a bound method would.

So yes, it does have some degree of coupling between data and behavior, and in effect, you're just extending an object with new behavior. Semantically, this could be anything, but in practice, it's almost always defined somewhat method-like. (Even Trine defines its utilities like methods.) So if it quacks like a method, it's a method, not a function.

Oh, and I'd rather 3. be dropped from this proposal, and this proposal would rectify 2.

@dead-claudia
Copy link
Contributor Author

Given that this thread has sufficiently derailed* and the original question for all intents and purposes answered (there's too little consensus on what problems this proposal's bind half is actually solving), I'm closing this.

* I'm not pointing fingers or judging anyone for it, since I myself helped it along. 😄

@dantman
Copy link

dantman commented Aug 31, 2017

Note that " Doesn't work well with libraries like Lodash." is false/irrelevant.

lodash-bound already exists, so it's entirely possible to use lodash with the current :: syntax (in fact I already am doing that).

Secondly the only reason lodash itself hasn't already included an official version of this is the fact that this proposal is stage-0. The moment this proposal looks like it'll become official syntax lodash would look at this syntax again to support it.

@dead-claudia
Copy link
Contributor Author

@ALL Can we just let this thread die?

@zenparsing Since the pipeline operator proposal is down a similar vein for the common use case of this, I feel this proposal could probably stand to be recast now, to not try to duplicate that effort.

@hax
Copy link
Member

hax commented Sep 1, 2017

To clarify, here's some of its shortcomings (and why we need a better proposal):

this isn't easily aliased - #30

Have no idea why "explicit this" will block this proposal.

::foo.bar !== ::foo.bar - #46

Just drop ::x.y part.

People expect ::object.foo to be object::foo - #18, #43, #8

ditto.

I'm not the only one finding the use of this to be awkward - #31

this already in JS for decades. And If someone do not use this they can just ignore bind operator.

The common case is just calling the function, hence why I filed #44

I agree. So what's the next step?

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

No branches or pull requests