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

Drop the this binding altogether #44

Open
dead-claudia opened this issue Jan 25, 2017 · 84 comments
Open

Drop the this binding altogether #44

dead-claudia opened this issue Jan 25, 2017 · 84 comments

Comments

@dead-claudia
Copy link
Contributor

dead-claudia commented Jan 25, 2017

Edit: Here's specifically what I'm proposing:

  • Keep the function pipelining: object::func(foo, bar, baz), equivalent to func.call(object, foo, bar, baz)
  • Drop the binary binding operator: object::func, equivalent to func.bind(object)
  • Drop the unary binding operator: ::object.func, equivalent to object.func.bind(object)

Note that this is independent of the syntax or semantics of the function pipelining itself (see #26 for an in-depth discussion on that and the old binding operator, and #40 for a more recent proposal specific to that), and is exclusively focused on dropping the binding syntax.


The resource usage for general this binding leads to a lot of confusion:

// This works
var listener = ::console.log

elem.addEventListener("click", listener, false)
elem.removeEventListener("click", listener, false)

// But this doesn't
elem.addEventListener("click", ::console.log, false)
elem.removeEventListener("click", ::console.log, false)

Those of us familiar with the proposal would immediately know this, but those less familiar would expect the opposite (which would be much more wasteful of memory, because JS doesn't have the lifetime management awareness of Rust and C++).

Also, for the binary version, object::method without an immediate call doesn't really help much at all for expressiveness.

Here's what I propose: drop the this binding altogether, and keep it limited to just immediate calls. I'm not proposing anything about where the object is passed (using this/first argument/etc.), or even the operator used, just not including binding in the proposal.

First, here's the equivalents for each one, using Function.prototype.bind and arrow functions, just for comparison:

// Property binding
::console.log
console.log.bind(console)
value => console.log(value)

// Function binding
object::method
method.bind(object)
value => method.call(object, value)

Function.prototype.bind is already easily optimized for a single argument. In addition, property and method binding is rarely chained (I've never seen it done, ever). How often do you see anything like this? Chances are, probably never, or if you have, it was likely an obvious refactor.

method.bind(console.log.bind(console))
(::console.log)::method // Using the current proposal

Additionally, if you need to bind after chaining, it's fairly easy to just create a new variable, and naming isn't usually hard for simple cases like these.


But the method chaining still has its benefits:

  1. Method chaining will avoid ugly nested calls like this:

    // Now
    var list = _.uniq(_.flatten(_.map(items, v => v.getName())))
    // Better
    var list = items
        ::_.map(v => v.getName())
        ::_.flatten()
        ::_.uniq()

    That would make things way easier to read, because the steps are more sequential, and fewer parentheses are involved. Functional languages frequently have something like this for similar reasons, like Elm/LiveScript/OCaml/F#'s x |> f operator. They helpfully avoid parentheses due to their low associativity and left-to-right application, making logic much easier to read.

  2. Wrapper libraries can leverage ES modules and Rollup's tree-shaking feature to not ship more than necessary, yet still retain the convenient pseudo-method syntax. You could create a Lodash clone that only calls the methods you need, so if you only use map, filter, reduce, and forEach, you only bundle those at runtime, even when you install the whole library from npm. Basically, what you don't use, you don't pay for.


So I still want the binary version to allow calls, but let's get rid of the this binding. It's confusing and unintuitive for newcomers, and hardly provides any benefit in practice.

@Artazor
Copy link

Artazor commented Jan 25, 2017

Or even return to the earlier proposal for the foreign properties (not a bind at all for the obj::prop)

var prop1 = Symbol();
var prop2 = { get: function() { return this::prop1 + 1 } };
var x = {};
x::prop1 = 123; // assign (same mechanics as WeakMap)
console.log(x::prop2); // 124  - retrieve (like if prop2 is defined as property on x)
// and x is still empty 

postulate that obj::f === f if typeof f === "function" and forget abound bind.
the only obj::f(...args) should be threated as f.call(obj, ...args)

just thoughts....

@MeirionHughes
Copy link

MeirionHughes commented Jan 25, 2017

The main issue as I see it is the chaining. Its the easy sell because you can clearly define a problem that needs solving.

Given something along the lines of:

function* where($this, predicate) {
  for (var item of $this) {
    if (predicate(item))
      yield item;
  }
}
function* select($this, selector) {
  for (var item of $this) {
    yield selector(item);
  }
}
function first($this) {
  for (var item of $this) {
    return item;
  }
  throw Error("no items");
}

if you try to chain these at present you get:

let ugly = first(select(where([1, 2, 3, 4, 5], x => x > 2), x => x.toString()));

which reads in complete reverse of what actually transpires. I think its quite clear that the full power of generators and Iterables are being held back because of this nesting.

A simple solution would be a mechanism that simply passes the left-operand, as the first parameter, to the right-operand function.

let better = [1, 2, 3, 4, 5]::where(x => x > 2)::select(x => x.toString())::first();

Also, he other criticism was that :: looked odd. Is single colon a better (available?) operand?

cc:@zenparsing

@dead-claudia
Copy link
Contributor Author

@MeirionHughes I'm only proposing dropping the rest, and just keeping the chaining. I'm intentionally steering away from talk regarding the :: syntax itself (and even its own semantics), because that's still a major point of contention that has gone absolutely nowhere in the past year. Feel free to comment on #42, and read up on the past discussion in #26, where the operator choice was discussed at length.

@bathos
Copy link

bathos commented Jan 27, 2017

For what little it’s worth, as someone who’s enjoyed using :: via babel for over a year, I have seen that the chaining form is, in addition to being very useful, pretty intuitive to other devs of various backgrounds. The unary form on the other hand has been a source of "huh" more than once. So while I personally saw no problem with the unary form, this (anecdotal) experience has led me to agree that dropping the unary is a good idea.

@mgol
Copy link

mgol commented Jan 31, 2017

Yet another argument in favor of dropping the unary form is it will be less controversial for the general public to swallow the binary form and it poses less spec dilemmas so it has better chances of landing in a spec quicker. The unary form could still land as a separate addition in a future ECMAScript version but coupling them in one proposal delays both of them.

@InvictusMB
Copy link

So is this suggestion about dropping the whole proposal in favor of giving :: a meaning of functional pipelining?
Is foo::bar going to be equivalent to _.partial(bar, foo) ?

@Artazor
Copy link

Artazor commented Feb 4, 2017

@InvictusMB, it means only that

we allow only the following syntax:

PrimaryExpression "::" PrimaryExpression "(" ArgumentList ")"

And treat foo::bar(a,b,c) as bar.call(foo, a, b, c)

Standalone foo::bar is prohibited (not yet specified)

Thus, no bindibg ever involved, and no new closure formed respectively.

@dead-claudia
Copy link
Contributor Author

dead-claudia commented Feb 4, 2017 via email

@Artazor
Copy link

Artazor commented Feb 4, 2017

And yes, it is kind of pipelining, but it uses a hidden this parameter.

I feel, that TC39 has very strange implicit resistance against any improvements related to any this usage cases.

@dead-claudia
Copy link
Contributor Author

dead-claudia commented Feb 4, 2017 via email

@InvictusMB
Copy link

InvictusMB commented Feb 4, 2017

@Artazor
bar.call(foo, a, b, c) doesn't fit the provided example from first post:

var list = items
    ::_.map(v => v.getName())
    ::_.flatten()
    ::_.uniq()

That example implies that result of LHS expression is partially applied to function in RHS and the resulting function is invoked with arguments in parentheses. Essentially LHS expression is being pipelined as a first argument to RHS instead of being passed as this in case of binding.
As per provided example foo::bar(a, b, c) should be interpreted as bar.call(null, foo, a, b, c)
Prohibiting standalone usage doesn't make sense as it is easy for an interpreter to omit partial application where possible and simplify to fn.call.

@isiahmeadows
I can agree that pipelining as a parameter is more appreciated than this proposal. But I see value in both options. They cover different use cases and programming styles.

Pipelining as a parameter will help people coding in functional style, while pipelining as this with bind operator (as per this version) is more helpful in the realm of OOP style.

In functional style you want to start a chain with a value and transform it through the chain of functions. You don't care about this at all as you probably never use function keyword and prefer => functions. The only context for your functions is lexical scope. You pass around a state as plain JS objects or with closures.

But in OOP style you want to start a chain with an object and invoke a chain of methods. You care about this a lot. It's a core concept to your mental model. You rely less on closures and more on passing the context around in this. Your functions are mostly methods. They are tightly coupled to their instances and not meant to be used without an instance.
Sometimes you want to invoke functions as if they where own methods of particular instance. In that particular situation you want to temporary extend your instance with new capabilities but creating a decorator for that is too much of a boilerplate. That is where extension methods are handy.

Occasionally you also want to pass around methods while preserving their binding to the owning instance. This happens when OOP style code meets functional style code.

@Artazor
Copy link

Artazor commented Feb 4, 2017

@InvictusMB I'm afraid that in the initial post :: is used like a functional pipelining operator. Actually, it was not a proposed semantics. I'd rather use another operator for that, e.g. ~>
Where a~>f(b) is the same as f(a,b).
However the initial intention of :: was a::f(b) as f.call(a,b). And exactly that implementation was already tested by Babel and TypeScript team. Everybody who used that form found that it is super-convenient. Especially for Observable operators. @isiahmeadows correct me if I'm wrong -)

@Artazor
Copy link

Artazor commented Feb 4, 2017

(sorry, just reformated my message - typing on phone is terrible)

@InvictusMB
Copy link

@Artazor
I was initially as confused by this thread as you are. Therefore my original question.

Nevertheless, I can envision the need for 3 different operators:

  • Functional pipelining (elm |> operator or sperm operator ~>) for functional style chaining
    a~>f(b) -> f(a,b)
    a~>f -> _.partial(f, a)
    Note _.partial as in lodash instead of f.bind(null, a) to not affect this

  • this pipelining with method resolution (:: operator) for OOP style chaining
    a::f(b) -> (a.f).call(a,b)
    a::f -> (a.f).bind(a)

  • this pipelining with foreign method (::@ operator) for method extension
    a::@f(b) -> f.call(a,b)
    a::@f -> f.bind(a)

They all have their respective use cases and fit for different programming styles.

  • Functional
const {map, flatten, uniq} = _;
const names = items
  |> map(item => item.value)
  |> flatten()
  |> uniq()
  • OOP
const foo = {
  bar() { return this; }
  bang() { return this; }
};

function bang() {
  return this::foo()::bar();
}

foo
  ::bar()
  ::@baz()
  ::bang()

I don't have a preference for either |> or ~>. But I have a strong opinion on :: operator. It is convenient to use :: for method access and it is widely accepted as a scope resolution operator .
For extension methods I would go for ::@ or ::$ or anything else starting with :: to emphasize their semantic connection.

In addition to that, always using :: for method access will clearly state an intention of accessing a method of an instance in contrast to using . for accessing a function and treating object as a namespace.
So seeing _.mapValues(foo) will tell you that _ is an utility library and mapValues is not using this. While seeing console::log(foo) will tell you that log relies internally on this being compatible with console.
Also arrayLike::@Array.prototype.sort() will tell you that it extracts a sort method from Array.prototype and invokes it as arrayLike's own method and that sort relies on the fact that during execution its this has to be array-like.

@MeirionHughes
Copy link

MeirionHughes commented Feb 4, 2017

I don't want to put words in ts39's mouth but if they found :: to (quote) look weird (unquote) then hopefully they'll say the same for |> because I'm not a fan. Primarily because it requires two hands to type |>, while :: or ~> can be done solely by the right hand.

@bathos
Copy link

bathos commented Feb 5, 2017

@MeirionHughes You’ve got me curious: how is it that ~> may be typed with one hand yet |> cannot? I imagine we must have different kinds of keyboards or different typing styles, because for me, ~> implies two hands, :: implies one, and |> could go either way. (This factor doesn’t matter to me personally one way or the other; they’re all equally fine to me, but it made me wonder.)

@Volune
Copy link

Volune commented Feb 5, 2017

It is convenient to use :: for method access

I'm not a fan of :: to access methods, as there are other ways to access a method. How will this operator work in these cases:

const foo = [ a => a + 1 ];
// How to do foo::0(42) or foo::[0](42) ?

const symbol = Symbol();
const bar = {
  [symbol](a) { return a + 1; },
  symbol() { throw new Error(); },
  baz(a) { return a + 2; },
};
// How to do bar::symbol(42) or bar::[symbol](42) ?

// How about bar::.baz(42) ?

If :: does not care about accessing methods, at least we can probably already do:

foo
  ::foo.bar()
  ::baz()
  ::foo.bang()

// Or maybe
foo
  .bar()
  ::baz()
  .bang()

And to me that's a great improvement by itself.

@InvictusMB
Copy link

@MeirionHughes @bathos
It's indeed keyboard layout specific. But unless you are producing write-only code and getting paid for the number of characters this should be the least of concerns. The major attention should go to readability, semantics and cognitive load which all contribute to maintainability of a code.

@Volune
Why would you pull [] operator into this discussion? Introducing :: neither prevents you nor changes the way you use . or [] operators. :: should be responsible only for reliable pipelining of context through this. That's the only difference from . and you don't use . with symbols either. If you want to use symbols or strings for property access, you would still need [] operator.
But if you really want context pipelining with brackets notation then you are talking of another operator ::[]. Which may be a valid suggestion on its own but doesn't help this discussion to conclude.

@dead-claudia
Copy link
Contributor Author

dead-claudia commented Feb 5, 2017 via email

@InvictusMB
Copy link

InvictusMB commented Feb 5, 2017

@MeirionHughes

I don't want to put words in ts39's mouth but if they found :: to (quote) look weird (unquote)

If I remember correctly that was related to the unary prefix form of ::. And in that context I agree with them. ::console.log is weird and crappy in many ways.

@Volune
Copy link

Volune commented Feb 5, 2017

@InvictusMB I'm not really pulling the [] operator. Having the "this pipelining operator" doing method resolution is already part of the discussion. I'm just pointing out that, with the current description, it will not cover all the possible ways of resolving a method on an object (i.e. not methods accessed with a symbol or number)

If :: (or whatever syntax, ☺> if you want) "this pipelining operator" never does method resolution, then it's simple.

  • A::B() gives B.call(A), never A.B::A(); A::B gives B.bind(A), never A.B.bind(A)
  • Need method resolution, do it the way you want: A::A.B(); A::A[symbol](), A::A['method'](), ...

And I doubt it prevents a future introduction of an operator that does method resolution.

@InvictusMB
Copy link

@Volune
:: doesn't have to support all possible ways of method resolution.
The use case you are describing still involves the name resolution it's just different. You are talking of lexical scope name resolution. That is what I called ::@ operator here.
It doesn't make things simple it just presents another use case.
There is a use case for pipelining with lexical scope name resolution and there is a use case for pipelining with instance scope resolution.
I would use :: for instance scope resolution and ::@ for lexical scope resolution. But that's a matter of naming. It doesn't change the fact that both versions have value.

@bathos
Copy link

bathos commented Feb 6, 2017

Is method resolution really being considered? It seems pretty much orthogonal to the point of this proposal to me and I don’t get how it entered the picture. Aside from seeming like a weird feature to graft on here, I believe the only precedent for manipulating scope resolution in JS is the deprecated with statement — would this not suffer from the same problems?

@hax
Copy link
Member

hax commented Feb 7, 2017

@InvictusMB I suggest use :: for lexical scope resolution in the discussion because most people familiar with this semantic thank to Babel.

Another reason is instance scope resolution seems not have very big value compare to lexical scope resolution (just my feeling). And I believe (and agree) it's why this issue suggest to drop them at current stage.

@dead-claudia
Copy link
Contributor Author

@bathos According to the proposal, object::func(...args) is equivalent to func.call(object, ...args). I'm proposing dropping the binding half of the proposal, though.

I've also updated my original proposal to clarify.

@bathos
Copy link

bathos commented Feb 8, 2017

@isiahmeadows That was my understanding, and I think it’s a wise plan.

What I was commenting on is that recently, both in this thread and in other threads on this board, there’s been discussion of using :: for runtime scope resolution that may be property access or regular binding resolution, a concept which seems pretty unrelated to this proposal to me(?). While I’m not worried that TC39 would approve that, I was worried by the statement that "doing method resolution is already part of the discussion", since it’d be a pity to see the original idea fail on account of adding such.

Do we know why this is coming up repeatedly? Is it that the :: symbol too heavy with baggage from other languages that use it with a different meaning?

@dead-claudia
Copy link
Contributor Author

@bathos

Do we know why this is coming up repeatedly? Is it that the :: symbol too heavy with baggage from other languages that use it with a different meaning?

I think it's mostly the reliance on this that's stirring people up, but yes, the symbol is also part of it. I've specifically been discouraging discussing that in this bug to avoid it derailing (as several already have). I'd love to share my theories on why that could be happening, but only in a different issue.

@fatcerberus
Copy link

As I hinted with #45, I think the binding operator would be more useful (and less confusing to C++ devs) if it were changed to mean lhs.rhs.bind(lhs). Then it would have an actual use case: delegates. One could pass this::method as a callback from within a class. Chaining/pipelining IMO should be a different symbol.

@Alxandr
Copy link

Alxandr commented Feb 8, 2017

Then it would have an actual use case

I'm sorry. Have you met functional programming before? Not to mention that even with this not being accepted in forever, there is already libraries out there written to use the pipelining.

@fatcerberus
Copy link

I think what confuses me most about the prefix syntax is that ::foo.bar looks like a unary operation on foo.bar but in reality is a binary operation over foo and bar. How does that even work out for precedence? I guess you would parse it as a ternary operator with no first operand, which is strange to say the least.

@zenparsing
Copy link
Member

If we can clear up some of the issues with the pipeline operator (multiple arguments and integration with method calls) I would completely be in favor of changing x::y to do method extraction.

@acutmore
Copy link

I think what confuses me most about the prefix syntax is that ::foo.bar looks like a unary operation on foo.bar but in reality is a binary operation over foo and bar. How does that even work out for precedence? I guess you would parse it as a ternary operator with no first operand, which is strange to say the least.
@fatcerberus

A similar thing could be said about the delete keyword. delete obj.foo operates on obj using 'foo' as an argument. This is achieved because obj.foo returns a reference that has two internal slots: the base (obj) and the referenced name (foo).

That way ::foo.bar is a unary operation on the reference returned by foo.bar.

@fatcerberus
Copy link

@acutmore Yeah, I always found delete’s semantics odd for that exact reason. Looking at a delete expression I don’t intuitively expect it to work and always second-guess it.

@leemotive
Copy link

in my opinion

  • obj::func equivalent to func.bind(obj)
  • obj::func(bar) equivalent to func.bind(obj, bar)

function pipelining should be written as obj::func(bar)(foo) and equivalent to func.bind(obj, bar)(foo) also equivalent to func.call(obj, bar, foo)

make obj::func(bar, foo) equivalent to func.call(obj, foo), apply will fill unhappy

@hax
Copy link
Member

hax commented Mar 6, 2018

@leemotive So it's just syntax sugar, what's the benefit of using it instead of func.bind(...)?

Anyway, this proposal seems already dead? What's the future plan, @zenparsing ?

@ljharb
Copy link
Member

ljharb commented Mar 6, 2018

@hax syntax is robust against delete Function.prototype.bind and similar, which is very critical.

@hax
Copy link
Member

hax commented Mar 7, 2018

@ljharb Most things in JS can be delete , eg. delete Object.is. Will committee consider introduce a is b operator?

@ljharb
Copy link
Member

ljharb commented Mar 7, 2018

@hax i would love to see that happen, yes. However, the slippery slope argument doesn't invalidate this specific syntactic improvement.

@hax
Copy link
Member

hax commented Mar 7, 2018

the slippery slope argument

@ljharb Sorry, it's not my intention. I gave this example because past TC39 decisions make me a impression that TC39 would very unlikely to introduce any pure syntax sugar which do not have other significant benefit. And I remember Object.is vs a is b operator has been discussed before, so I have a impression that immune from delete Object.is was not a significant benefit.

I'm wondering if the taste of TC39 has changed now?

@acutmore
Copy link

@hax one of the interesting properties of syntactic sugar in a dynamic language is that it can open up compiler improvements. So not only can the code be clearer to the reader but also it gives the compiler more information to work with.

For example adding await to javascript could be seen as just sugar over using .then but it allowed v8 to produce much clearer call stacks: https://mathiasbynens.be/notes/async-stack-traces

I would imagine this bind syntax might also open the door for some optimisations

@hax
Copy link
Member

hax commented Mar 13, 2018

@acutmore await is not a pure sugar as my understanding. For example, it's very troublesome to convert await to .then if there is flow control like throw/return/break/continue...

@hax
Copy link
Member

hax commented Mar 13, 2018

If we plan to repurpose a::b to method extraction as #44 (comment) , there is also another problem: we need to support both member access (a.b) and computed member access (a[b]).

In the original proposal, we use ::a.b and ::a[b] which is consistent, but if change to a::b and a::[b], we will face the same problem like currently controversial syntax issue of optional chaining operators.

For the consistency, we'd better drop :: syntax. I think we could use a.☐b and a.[☐b] which ☐ is a possible char. For example, use &, it will be a.&b a[&b]. (Groovy use a.&b for similar purpose.)

@zenparsing
Copy link
Member

@hax

In the original proposal, we use ::a.b and ::a[b] which is consistent, but if change to a::b and a::[b], we will face the same problem like currently controversial syntax issue of optional chaining operators.

Great observation! And I agree completely. I would still like to see if we can provide some kind of syntactic support for method extraction, but it will likely need to be a unary operator. We probably don't want to have a sigil inside of the brackets, though.

Maybe just a prefix &, somehow?

let a = &obj.x;
let b = &obj['x'];
let c = &obj->x; // ;)

@fatcerberus
Copy link

fatcerberus commented Mar 13, 2018

I could get behind unary &. Double-colon :: as a unary operator puts me off big time since I don't think there's any other mainstream language where the operator works that way. By contrast & is the addressof operator in at least C/C++ so I think &obj.x would be pretty nice.

@hax
Copy link
Member

hax commented Mar 13, 2018

@zenparsing Prefix & will introduce new ASI problem 😢 Though it seems we will unlikely meet it in current code.

a  // <- missing ;
&obj.x // useless now...

But maybe future:

a  // <- missing ;
&obj.x |> accept_a_func

@zenparsing
Copy link
Member

@hax Yes, I thought about the ASI issue, but (as with prefix + and -) it may be acceptable depending on usage patterns. Semicommon-light linters would have to be upgraded to warn on & on a newline without a preceding ;.

@hax
Copy link
Member

hax commented Mar 13, 2018

@zenparsing Yes, it just add another new token to -+/([ list 😅

Another reason I prefer a.&b to &a.b is: when people program, it seems many would realize they need extract the method after they entered a. so it's a little pain to backwards the cursor.

This is my subjective feeling, but groovy's a.&b design may reflect my guess 🧐

I even consider postfix operator may be better though there is not much choice of token...

@lukescott
Copy link

Since the pipeline operator/proposal handles the chaining use-case, what's wrong with obj->x? It closely resembles the fat arrow => syntax, so it could easily be explained that "arrows mean bind".

@hax
Copy link
Member

hax commented Mar 14, 2018

@lukescott obj->x seems good, but it also face the inconsistent problem of obj->[x], see #44 (comment)

@cshaa
Copy link

cshaa commented Mar 18, 2018

To me, the most important use case of the function pipelining a::b() are extension methods. In languages like C#, you can define extension methods that look as if they were part of the object itself. This would pair nicely with ES6 modules:

import { distinct, join, sort } from 'linq';

let a = [ 8, 42, 5, 9, 2, 5 ];

a::distinct()
 ::sort( x => -x )
 ::join(", ")
 |> console.log;

The reason why I prefer the :: syntax over the -> operator is that it is similar to the . operator used to access properties – a::sort() is almost like a.sort(). However I agree that the proposal is getting very close to the pipeline proposal and the syntax should probably reflect the similarity.

Therefore I propose changing the operator for “this pipelining” from :: to .>, as it's similar to both the pipeline operator |> and the member access operator .. Then we should also merge it with the proposal-pipeline-operator and leave this repository for the unary operator only.

@cshaa
Copy link

cshaa commented Mar 19, 2018

On tc39/proposal-pipeline-operator#110 I've done a summary of the similarities and differences between the Bind Operator and the Pipeline Proposal and I found out that the part promoted by @isiahmeadows is really the only part we need. The rest can be quite easily done with the Smart Pipelines. Be sure to check the issue out, as this is IMHO the most probable way of getting the “function pipelining”, as Isiah calls it, to the specs!

@hax
Copy link
Member

hax commented Mar 20, 2018

IMO, pipeline is different and can't replace bind operator. Check my comment at
tc39/proposal-pipeline-operator#101 (comment) .

@michaeljota
Copy link

This proposal maybe refocus to cover #5 mainly, as I think that use case can't be covered by the pipeline operator.

TL; DR: Use bind operator on destructive functions assignment.

function foo({ ::doA, ::doC }) {
  doA();
  doC();
}

const bar = {
  a: 1,
  c: 2,
  doA() {
    console.log(this.a);
  },
  doC {
    console.log(this.c);
  }
}

foo(bar); 
// Output: (With bind operator)
// 1
// 2

// Output: (Without bind operator)
// undefined
// undefined

I think this would fit nicely between functional programming, and the oop spirit of Javascript.

This currently could be done with something like this

function foo(bar) {
  const { doA, doC } = bar;
  doA.bind(bar);
  doC.bind(bar);
  doA();
  doC();
}

But it's not practical, as I would need n + 1 lines for every function I would like to do this. This could even be used with renamed assignment

function foo({ ::doA: baz }) {
  baz
}

In #5 was a discussion about where the binding should belong, and I think the binding should belong to the destructive property (doA), as baz is just a variable assignment.

Don't know what you think about this, or if you just want to let this die completely.

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