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

Reply via email to