> I'm just re-iterating, but `Object.assign()` is all we need losing getters and setters where super is also allowed is all we *don't* need as well, not sure why keep ignoring the fact Object.assign has undesired side-effects.
On Mon, Aug 1, 2016 at 8:30 AM, Michał Wadas <[email protected]> wrote: > fn.bind().bind() can't throw because of backward compatibility. > > On 1 Aug 2016 9:21 a.m., "/#!/JoePea" <[email protected]> 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 >> [email protected] >> https://mail.mozilla.org/listinfo/es-discuss >> >> > _______________________________________________ > es-discuss mailing list > [email protected] > https://mail.mozilla.org/listinfo/es-discuss > >
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

