The `super` keyword doesn't work as it should?
/#!/JoePea
joe at trusktr.io
Tue Aug 2 05:28:49 UTC 2016
> fn.bind().bind() can't throw because of backward compatibility.
Well, that's fine with me. The important thing to consider would be those
other things I listed that shouldn't throw. These ones:
- `func.bind(...)` does not throw
- `func.toMethod(...)` does not throw
- `func.bind(...).toMethod(...)` does not throw
- `func.toMethod(...).bind(...)` does not throw
As for the throwing cases, I don't really care if they throw. They could
also just silently fail. What I'm pointing out here is that it should be
possible to bind both`this` and `HomeObject` and not be restricted just
because one or the other was already bound.
*/#!/*JoePea
On Mon, Aug 1, 2016 at 12:30 AM, Michał Wadas <michalwadas at gmail.com> wrote:
> fn.bind().bind() can't throw because of backward compatibility.
>
> On 1 Aug 2016 9:21 a.m., "/#!/JoePea" <joe at 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 at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160801/b49fe753/attachment-0001.html>
More information about the es-discuss
mailing list