fn.bind().bind() can't throw because of backward compatibility. On 1 Aug 2016 9:21 a.m., "/#!/JoePea" <j...@trusktr.io> wrote:
> Allen, I read your linked documents. After thinking about it more, I > believe it would be ideal to have both: > > 1. a tool like `toMethod`. I like "setHome" better as that describes much > more concisely what is happening. `.bind()` is already a way to make a > function *behave as a method* of the object passed to `.bind()`, so > `toMethod` clashes with that. Also, we're not really assigning a method > onto an object with `toMethod`, so it seems awkward. We're setting the > HomeObject, that's what we're doing. > 2. and a dynamic `super`. > > > `func.bind().toMethod(...)` should throw > > I believe that `.bind` and `.toMethod` are useful not just for keeping > contexts, but so that when passing a function away from our own processes > to external processes those external processes cannot tinker with `this` > and `super`. The external processes may store the functions inside objects, > and we may deem it important that the passed functions act upon the context > we specify instead of those storage containers. This is one thing arrow > functions solve regarding `this` and `super` (more on that below). But, if > "`func.bind().toMethod(...)` should throw", then how does one bind both > `this` and `HomeObject` in order to guarantee that some external process > doesn't modify one or the other (or for some other reason)? > > Thus, if `let func = function() {}`, it might make sense for the following > to be true: > > - `func.bind(...)` does not throw > - `func. > toMethod > (...)` does not throw > - `func.bind(...).toMethod(...)` does not throw > - `func. > toMethod > (...). > bind > (...)` does not throw > > and errors should be thrown only when `bind` or `toMethod` are called on > something that is already `bind`ed or `toMethod`ed, respectively: > > > - `func.bind(...). > bind > (...)` > throws > - `func. > toMethod > (...) > .toMethod > > (...)` throws > - `func.toMethod(...). > bind > (...) > .bind(...) > ` throws > - `func.toMethod(...).bind(...). > toMethod > (...)` throws > - `func. > bind > (...). > toMethod > (...). > toMethod > (...)` throws > - `func. > bind > (...). > toMethod > (...). > bind > (...)` throws > > It would also be important to consider that passing > `this.func.bind(...)` to an external process still leaves `super` up for > modification, and vice versa if passing `this.func.toMethod(...)`. > > If someone has done `this.func.bind()` and has no idea what to pass to a > following `.toMethod` call in order to prevent external processes from > tinkering with it (which will be the case most of the time because > requiring the user to lookup where on the prototype chain a method is > located is requiring way too much work of them in order to achieve a simple > outcome), then maybe there can be a new `.lock()` method that simply > returns a new function `bind()`ed and `toMethod()`ed to whatever `this` and > `super` are relative to the object passed to `lock`. For example, > > ```js > $('.some-el').on('click', this.func.lock(this)) // binds both `this` and > `HomeObject` > ``` > > would be essentially equivalent to > > ```js > $('.some-el').on('click', this.func.bind(this).toMethod( findHome(this, > 'func', this.func) )) > ``` > > where `findHome` would be a necessary helper to have laying around, which > proves that `toMethod` is not ideal for average cases, only for people > involved in meta-programming. > > The "issues with toMethod" in your [mixin-proposal.pdf]( > https://github.com/tc39/tc39-notes/blob/master/es6/2015-03/mixin-proposal.pdf) > seem like implementation details that don't need to surface to the end user > of the language. For example, consider the issues of `toMethod` > > > - Requir[ing] copying > > - Hav[ing] the "deep clone" problem > > I don't think copying or cloning is necessary. Internally, the engine > could use a proxy that simply passes to the wrapped internal function > `this`, `HomeObject`, or both, depending on which are bound, otherwise the > engine falls back to contexts based on `this` and where in the prototype > chain from `this` that `super` is being used (that algo doesn't seem > expensive since the LookUp algo already finds the prototype (HomeObject) > where a property lives, assuming `super` to be dynamic). > > Internally, the proxy returned from `func.bind(...)` would contain the > reference to the `this` argument, and leave `super` to the lookup algo > starting lookup at that `this` context. Internally, the proxy returned from > `func.toMethod(...)` would contain the `HomeObject` reference to pass into > function calls of the wrapped function. The things returned are proxies, so > property lookup could be forwarded without having to worry about cloning. > We could call `.toMethod()` or `.bind()` on either of those two proxies, > respectively, in which case the proxies record the second `HomeObject` or > `this` arguments, respectively. Any more calls and an error can be thrown. > > Implemented along those lines, there doesn't need to be any copying or > cloning. (Maybe I'm missing why that is required, but it doesn't seem to be > required the way I'm imagining it). > > To make `.bind()` and `.toMethod()` *not* seem like magic (because > currently `.bind()` returns a function who's direct prototype is > `Function.prototype`, which is somewhat like magic and makes it foggy how > the new function is related to the old one), there could be a new > `FunctionProxy` class (it might extend from `Function`, or `Proxy` *and* > `Function` if there were multiple inheritance). It would not be possible to > get a reference to the original function from the `FunctionProxy` so that > unauthorized manipulation of `this` and `super` can be guaranteed when that > is what is wanted. > > So, the following snippets would be effectively the same: > > ```js > let f = function() {console.log(this, super)} > f = new FunctionProxy() > f.bind(this) > f.toMethod(findHome(this, 'nameOfMethodWeAreIn', this.nameOfMethodWeAreIn)) > ``` > > ```js > let f = function() {console.log(this, super)} > f = f.bind(this) // returns a FunctionProxy > f = f.toMethod(findHome(this, 'nameOfMethodWeAreIn', > this.nameOfMethodWeAreIn)) // returns the *same* FunctionProxy. > ``` > > ```js > let f = function() {console.log(this, super)} > f = f.bind(this).toMethod(findHome(this, 'nameOfMethodWeAreIn', > this.nameOfMethodWeAreIn)) > ``` > > ```js > let f = function() {console.log(this, super)} > f = f.lock(this) // The lock method finds the home of `f` relative to > `this` if any, otherwise sets HomeObject to `this`, or similar. > ``` > > The combination of `bind` with `toMethod` (or a new `lock` method) in > those examples shows how to achieve essentially the same as with arrow > functions, so > > ```js > let f = function() {console.log(this, > super)}.bind(this).toMethod(findHome(this, 'nameOfMethodWeAreIn', > this.nameOfMethodWeAreIn)) > ``` > > is effectively equivalent to the following using arrow functions > (disregarding `arguments` and `new.target`): > > ```js > let f = () => {console.log(this, super)} // <-- yes! > ``` > > The [mixin-proposal.pdf]( > https://github.com/tc39/tc39-notes/blob/master/es6/2015-03/mixin-proposal.pdf) > mentions that > > > Programmers don't need to think about or even be aware of the internals > of super. > > Programmers are currently aware of needing to sometimes bind functions > passed to other libraries, or to bind functions in order to make them > operate on certain objects as if they were methods of those objects, etc. > > Well, considering developers know this about `.bind()`, the nice thing > about passing `obj.func.bind(obj)` to an external process is that the > external process can call the passed function and that call will be > equivalent to calling `obj.func()` in the original process, > *and therefore a dynamic `super` will still work as expected without > programmers having to actually use `toMethod`* > because lookup for `super` would start at the prototype chain leaf which > is referenced by the `this` that was bound. This concept is backwards > compatible with pre-ES6 `.bind()` usage, and so people coming from ES5 and > using `.bind()` in ES8 or ES9 (or wherever dynamic `super` lands) will not > be surprised, and it will be intuitive. > > Note that passing the bound function in the last paragraph still leaves > the function susceptible to being called with a tinkered `HomeObject`. > > `toMethod` will be mostly useful for interesting cases where people are > implementing frameworks or tools and need to have control over classes or > inheritance schemes are implemented, or something meta-programming like > that. But, people simply defining classes will never need to worry about > `super` internals even if it is dynamic and if `toMethod` exists. > > A dynamic `super` means that `super` would be allowed in any function > definition, not just object-initializer methods or class definition > methods, which makes it easy to `Object.assign()` things, copy methods > ad-hoc, and basically enjoy ES5-like techniques. ES6 classes and > object-initializers should be limited to being syntactical shortcuts as > much as possible, and not impose limitations on developers that cause > developers to have to abandon pre-ES6 techniques (copying methods from one > prototype to another for example) just because something like `super` is > unfortunately static. > > ### This destroys the pre-ES6 flexibility that developers had in > manipulating prototypes because now they cannot apply the same techniques > with ES6 classes. > > It may be tempting for someone to refute this and say "well, then just > write your classes the ES5 way". That's fine and dandy, but then it means > my tools for manipulating ES5 classes will be highly incompatible with ES6 > classes (which are supposed to be "syntax sugar"). > > This is bad because it causes fragmentation in the ecosystem. Some > libraries will work only with ES5 classes, others with ES6 classes, and > some libraries will be incompatible with each other because the paradigm > has forked into two directions rather than ES6 being a pure extension of > ES5. > > ### If super is static, then should I care about prototypes anymore? Maybe > I shouldn't care about them anymore because they can't be manipulated > without `super` getting in the way. Should I stop thinking about JavaScript > as a prototypal language and abandon my efforts at manipulating prototypes > in order to create a multiple-inheritance scheme? Should I just write ES6 > classes and not think about prototypes and pretend JavaScript was never a > prototypal language? > > That is not what I want, and I bet highly that many of us here don't want > that, but that is the direction that a static `super` is taking us, and the > `mixin {}` operator idea is like a rocket propelled explosive going down > that wrong path in the paradigm fork that I mentioned just moments ago, > ready to cause fragmentation, as in "oh, I'll use `Object.assign` on my ES5 > classes, and `mixin {}` on my ES6 classes" or "darn, I wanted to use ES6 > classes because I like the syntax, but I can't because static `super` is in > the way. So much for syntax sugar.". (plus, ES6 constructors not being > callable is also in the way in my case, as I can't simply call them on > another `this` which I really want to do, but I'll save that for another > day, and will just use my [`Class().extends()` API]( > https://www.npmjs.com/package/lowclass) to mimic ES6 syntax in defining > all my classes, but then I will actually be able to implement my > `multiple()`-inheritance helper without needing `eval` and unfortunately > without using ES6 classes -- maybe I'll use ES9 or ES10 classes when > `super` is dynamic...) > > There's the current path (static `super`) and the path that is in-line > with ES5 (dynamic `super` and ES5-based tools like `Object.assign` behaving > intuitively). > > (Note, some may want a function mixed into somewhere to have it's original > HomeObject, and that is what `toMethod` can be useful for. Everyone can be > satisfied.) > > The world will be much better this way, people will have more freedom to > implement interesting things, and it will be more intuitive (`_.extend` > will keep working in cases where methods use `super`!); it's better for the > world to have this flexibility. Most developers will just use ES6 classes > without worrying about `super` internals, *even if* `toMethod` exists > and/or `super` is dynamic. `toMethod` usage will be rare, *but it will be > powerful when it is needed.* > > `toMethod` or a dynamic `super` will not be a footgun because most people > will never need to know or work with those details, plus with `super` being > dynamic it means JavaScript can behave the way we already expect from two > decades worth of using the keyword `this`, which I think is A REALLY GOOD > THING. > > It would mean that `this` and `super` can be used anywhere and we wouldn't > have to distinguish between functions and > methods-defined-by-object-initializer-shorthand (paradigm fragmentation). > The distinction is not intuitive. We can merge the paradigm fragmentation > by simply making `super` dynamic along with introducing the tool to > configure `HomeObject` on as-needed basis for the rare cases (and even if > it were a footgun, it's need is rare, and people who wield the footgun will > probably convert the footgun into a powerful tool of mass benefit). > > At the end of the day, it would be excellent for the following to work, > and is what would be intuitive, without requiring a new operator: > > ```js > let o = { > __proto__: other, > func() {super.func()} > } > ``` > > ```js > let o = { > __proto__: other, > func: function func() {super.func()} // this should literally be 100% > equivalent to the previous example > } > ``` > > ```js > let o = { > __proto__: other > } > Object.assign(o, { > func: function func() {super.func()} // and this should end up being > exactly equivalent too. > }) > ``` > > > Let's make mixing in methods as simple as `Object.assign(obj, {...})` > > Let's make it simpler with `mixin {...}` > > I'm just re-iterating, but `Object.assign()` is all we need, and new > operators are needed because they solidify the provably-unwanted and > unintuitive static `super`. Adding more to the language on top of static > `super` may cause more and more divergence between ES5 (things like > `Object.assign` -- I know it came out in ES6, but `_.extend` was out before > ES6) and ES6 (class-definition methods, object-initializer methods), and > fragment the language into two paths. A dynamic `super` solves the problem, > and it can be well documented so that developers will know what to expect > just like they already have with two decades of `this`. A dynamic `super` > is perfectly in line with the dynamic nature `this` (performance > considerations aside). > > If there really is a performance problem with a dynamic `super`, let's > just solve *that* instead of creating separate syntaxes that are > incompatible with the JavaScript we already know. A dynamic `super` is also > forwards and ahead of what `super` is in other languages. JavaScript > doesn't need to go in that direction, it needs to go furthermore forwards. > > Also keep in mind that a dynamic `super` will prevent surprises to > developers who are beginning to learn JavaScript in the post-ES6 era. > > We should strive for a dynamic `super` (which would be completely > backwards compatible with how it currently works) along with something like > `toMethod`. > > --- > > Does anyone mind pointing out the specific unwanted overhead costs of a > dynamic `super`? > > */#!/*JoePea > > _______________________________________________ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > >
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss