From b6210bbc0f89ded970892ee360962807b6f35826 Mon Sep 17 00:00:00 2001 From: Ryan Tablada Date: Wed, 20 Mar 2019 17:06:21 -0700 Subject: [PATCH 1/8] add bind helper draft --- text/0000-bind-helper.md | 124 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 text/0000-bind-helper.md diff --git a/text/0000-bind-helper.md b/text/0000-bind-helper.md new file mode 100644 index 0000000000..1db4d35a26 --- /dev/null +++ b/text/0000-bind-helper.md @@ -0,0 +1,124 @@ +- Start Date: 2019-03-20 +- Relevant Team(s): Ember.js, Learning +- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- Tracking: (leave this empty) + +# Bind Helper + +## Summary + +This RFC introduces a new helper `bind` to allow clear argument and context scope binding for functions passed in templates. + +## Motivation + +The current `action` helper has a lot of implicit and confusing behavior that is different than the Octane and post Octane programming model. + +To understand the complexity of `action` there are many complex behaviors including: + +1. Argument partial application (currying) +2. `this` context binding +3. `send` checks for Component and Controllers + +At build time the `action` helper currently is passed through an AST transform to explicitly bind the `this` to be deterministic at runtime. This is a private API where the outputted Glimmer is not a 1-1 to the template. Also, the `action` helper is confused and has overlap with the `action` modifier which has similar but slightly different behavior. + +Instead of this confusing and overloaded behavior, a new `bind` helper would be introduced to do partial application and context binding (with no need for build time private APIs). + +## Detailed design + +The `bind` helper will take in a function and then the set of arguments that will be partially applied to the function. +As an optional named argument the user can pass in a `this` property that will set the `this` context this named argument would default to `undefined`. + +The behavior of this helper will be simple and use JavaScript's `function.prototype.bind`. + +Here are some examples of the `bind` helper and the equivalent JS: + +### Simple Case On Argument Curry + +```hbs +{{bind this.log 1}} +``` + +```js +this.log.bind(null, 1); +``` + +### Multiple Argument Partial Application + +```hbs +{{bind this.add 1 2}} +``` + +```js +this.add.bind(null, 1, 2); +``` + +### Setting the `this` context + +```hbs +{{{bind this.session.logout context=this.anotherSession}}} +``` + +```js +this.session.logout.bind(this.session) +``` + +### Setting the `this` context and arguments + +```hbs +{{bind this.car.drive 1000 'mile' context=this.honda}} +``` + +```js +this.car.logout.bind(this.car, 1000, 'mile') +``` + +### Comparison to Action Helper/Modifier + +```hbs +{{!-- Actions --}} + + + + + + + + + +``` + +### With `mut` + +```hbs +{{!-- Actions --}} + + + + + + +``` + +Because `function.prototype.bind` always binds the `this` context the bind helper would require functions to be already bound or the `this` named argument would need to be explicitly set. + +## How we teach this + +For guides we would switch to recommending the `bind` helper to pass functions into components args and modifiers. +In guides we would no longer recommend using the `action` helper based on the reasons listed in motivations. + +## Drawbacks + +Since this recommended design sets the `this` context to `null` using `function.prototype.bind` this could be confusing to junior developers if the function was not already bound (using something like an `@bound` decorator, JS binding, arrow functions, or the `bind` helper earlier in the stack). + +## Alternatives + +One alternative would be to continue using the `action` helper despite confusion and overloading behavior. + +Another alternative would be to make the `bind` helper arguments exactly match `function.prototype.bind` (requiring `this` to always be passed in as the first argument). Doing this should probably introduce an `unbound` helper to allow partial argument application without changing `this`. + +A third alternative would be to make the `context=` behavior not bind the `this` context. The ability to do this would need some introspection of what the context is of the function call (essentially recreating the complexity of `action`). + +## Unresolved questions + +> Optional, but suggested for first drafts. What parts of the design are still +How does this feel with `mut` since we do not `value=` syntax (possibly an `extract` helper or some syntax to get args from events or nested objects). From b2c21a73c45faf7873a0adf5940c562337884354 Mon Sep 17 00:00:00 2001 From: Ryan Tablada Date: Wed, 20 Mar 2019 18:12:59 -0700 Subject: [PATCH 2/8] rfc url --- text/0000-bind-helper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-bind-helper.md b/text/0000-bind-helper.md index 1db4d35a26..4fcda73727 100644 --- a/text/0000-bind-helper.md +++ b/text/0000-bind-helper.md @@ -1,6 +1,6 @@ - Start Date: 2019-03-20 - Relevant Team(s): Ember.js, Learning -- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- RFC PR: https://github.com/emberjs/rfcs/pull/470 - Tracking: (leave this empty) # Bind Helper From 8830b1d70af96fcb1ec499ce9a85f8d78b20c293 Mon Sep 17 00:00:00 2001 From: Ryan Tablada Date: Fri, 22 Mar 2019 12:19:38 -0500 Subject: [PATCH 3/8] update examples and copy --- text/0000-bind-helper.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-bind-helper.md b/text/0000-bind-helper.md index 4fcda73727..d57c517a53 100644 --- a/text/0000-bind-helper.md +++ b/text/0000-bind-helper.md @@ -59,7 +59,7 @@ this.add.bind(null, 1, 2); ``` ```js -this.session.logout.bind(this.session) +this.session.logout.bind(this.anotherSession) ``` ### Setting the `this` context and arguments @@ -69,7 +69,7 @@ this.session.logout.bind(this.session) ``` ```js -this.car.logout.bind(this.car, 1000, 'mile') +this.car.logout.bind(this.honda, 1000, 'mile') ``` ### Comparison to Action Helper/Modifier @@ -108,7 +108,7 @@ In guides we would no longer recommend using the `action` helper based on the re ## Drawbacks -Since this recommended design sets the `this` context to `null` using `function.prototype.bind` this could be confusing to junior developers if the function was not already bound (using something like an `@bound` decorator, JS binding, arrow functions, or the `bind` helper earlier in the stack). +Since this recommended design sets the `this` context to `null` using `function.prototype.bind` this could be confusing to junior developers if the function was not already bound (using something like the `@action` decorator or an `@autobind` decorator, JS binding, arrow functions, or the `bind` helper earlier in the stack). ## Alternatives From 0b64a487a67cc6cdfda71e3fb6d99ceb8b1111b9 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Wed, 27 Mar 2019 15:10:26 -0700 Subject: [PATCH 4/8] Update to `with-args` --- text/0000-bind-helper.md | 76 +++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/text/0000-bind-helper.md b/text/0000-bind-helper.md index d57c517a53..660cd98401 100644 --- a/text/0000-bind-helper.md +++ b/text/0000-bind-helper.md @@ -3,11 +3,11 @@ - RFC PR: https://github.com/emberjs/rfcs/pull/470 - Tracking: (leave this empty) -# Bind Helper +# `{{with-args}}` Helper ## Summary -This RFC introduces a new helper `bind` to allow clear argument and context scope binding for functions passed in templates. +This RFC introduces a new helper, `{{with-args}}`, to allow clear argument passing for functions in templates. ## Motivation @@ -21,56 +21,41 @@ To understand the complexity of `action` there are many complex behaviors includ At build time the `action` helper currently is passed through an AST transform to explicitly bind the `this` to be deterministic at runtime. This is a private API where the outputted Glimmer is not a 1-1 to the template. Also, the `action` helper is confused and has overlap with the `action` modifier which has similar but slightly different behavior. -Instead of this confusing and overloaded behavior, a new `bind` helper would be introduced to do partial application and context binding (with no need for build time private APIs). +Instead of this confusing and overloaded behavior, a new `with-args` helper would be introduced to do partial application (with no need for build time private APIs), and context binding will be done instead using the `@action` decorator in classes. ## Detailed design -The `bind` helper will take in a function and then the set of arguments that will be partially applied to the function. -As an optional named argument the user can pass in a `this` property that will set the `this` context this named argument would default to `undefined`. +The `with-args` helper will take in a function and then the set of arguments that will be partially applied to the function. -The behavior of this helper will be simple and use JavaScript's `function.prototype.bind`. - -Here are some examples of the `bind` helper and the equivalent JS: +Here are some examples of the `with-args` helper and the equivalent JS: ### Simple Case On Argument Curry ```hbs -{{bind this.log 1}} +{{with-args this.log 1}} ``` ```js -this.log.bind(null, 1); +return function() { + this.log.call(this, 1); +} ``` ### Multiple Argument Partial Application ```hbs -{{bind this.add 1 2}} +{{with-args this.add 1 2}} ``` ```js -this.add.bind(null, 1, 2); -``` - -### Setting the `this` context - -```hbs -{{{bind this.session.logout context=this.anotherSession}}} +return function() { + this.log.call(this, 1, 2); +} ``` -```js -this.session.logout.bind(this.anotherSession) -``` +The use of `function` application like so allows us to preserve/pass through the `this` context of the calling site accurately, so creating a function `with-args` is equivalent to the same function _without_ args. -### Setting the `this` context and arguments - -```hbs -{{bind this.car.drive 1000 'mile' context=this.honda}} -``` - -```js -this.car.logout.bind(this.honda, 1000, 'mile') -``` +We will also reserve the `withArgs` helper name for future use as an alias, anticipating changes coming from template imports and most helpers/components conforming to valid JS identifiers. ### Comparison to Action Helper/Modifier @@ -83,8 +68,8 @@ this.car.logout.bind(this.honda, 1000, 'mile') - - + + ``` ### With `mut` @@ -95,30 +80,35 @@ this.car.logout.bind(this.honda, 1000, 'mile') - - + + ``` -Because `function.prototype.bind` always binds the `this` context the bind helper would require functions to be already bound or the `this` named argument would need to be explicitly set. - ## How we teach this -For guides we would switch to recommending the `bind` helper to pass functions into components args and modifiers. +For guides we would switch to recommending the `with-args` helper to pass functions into components args and modifiers. + In guides we would no longer recommend using the `action` helper based on the reasons listed in motivations. ## Drawbacks -Since this recommended design sets the `this` context to `null` using `function.prototype.bind` this could be confusing to junior developers if the function was not already bound (using something like the `@action` decorator or an `@autobind` decorator, JS binding, arrow functions, or the `bind` helper earlier in the stack). +* `with-args` is a bit of a verbose name, and given this is a commonly used helper it could be a papercut to users and make templates harder to read. Combined with the extra complexity of requiring the `@action` decorator to bind context, and the `{{on}}` modifier to trigger events, it could lead to users avoiding the helper altogether in favor of `{{action}}`. ## Alternatives One alternative would be to continue using the `action` helper despite confusion and overloading behavior. -Another alternative would be to make the `bind` helper arguments exactly match `function.prototype.bind` (requiring `this` to always be passed in as the first argument). Doing this should probably introduce an `unbound` helper to allow partial argument application without changing `this`. +There are also a number of potential alternatives for names: -A third alternative would be to make the `context=` behavior not bind the `this` context. The ability to do this would need some introspection of what the context is of the function call (essentially recreating the complexity of `action`). +* `args` - A shorter, simpler name with similar properties. This is somewhat less self-explanatory (on its own, without context, one might think it refers to component args, or does something with them), but may make up for this by being short and simple. +* `bind` - The original name this RFC suggested. `bind` is fairly _imperative_, it describes the action that we do to the function rather than what is returned. It also does not exactly match the JS method API, and as noted in the RFC feedback this could be confusing. Finally, it requires stopping to teach the concept of binding and how that works, which is a lot of overhead for a helper that will be used early and often. +* `call` - This reads nicely in templates, but is _very_ imperative and has already been confusing to folks when discussed. It differs significantly from the JS method API, teaching around this would be difficult, though possible. It also is not clear that it is _not required_ unless args are being passed, so we may see users attempting to use it for plain functions. -## Unresolved questions +Names that have been considered, but passed over: -> Optional, but suggested for first drafts. What parts of the design are still -How does this feel with `mut` since we do not `value=` syntax (possibly an `extract` helper or some syntax to get args from events or nested objects). +* `apply` - Same downsides as `call`, but less nice to read. +* `applyArgs` - Similar enough to `with-args`, but uses more obscure computer-science-y terminology without many benefits. +* `partial` - From LISP and other languages. `partial` there means "Partial Application". This is a computer-science-y term that isn't super explanatory, plus `partial` is already a (deprecated) feature in Ember templates. +* `papply` - From R. Generally unaesthetically pleasing, and same issues as `partial` +* `action` - We considered trying to reclaim the "action" term, but it still has the same problems of overlap with the modifier and decorator, and there isn't an easy transition path to deprecating the automatic `this` binding. +* `callback` - Considered, but it conflicts with the `@action` decorator's naming - the method is an action, until we pass it to callback, at which point it's a callback? This felt too confusing, and we believe it makes most sense if the helper is an "adjective" that modifies whatever its input is. From 7e491c36bb9485cc09591c20ea1c05f2c5ae1b2d Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Wed, 27 Mar 2019 20:05:53 -0700 Subject: [PATCH 5/8] remove context= --- text/0000-bind-helper.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/0000-bind-helper.md b/text/0000-bind-helper.md index 660cd98401..425282a490 100644 --- a/text/0000-bind-helper.md +++ b/text/0000-bind-helper.md @@ -61,15 +61,15 @@ We will also reserve the `withArgs` helper name for future use as an alias, anti ```hbs {{!-- Actions --}} - - - - - - - - - + + + + + + + + + ``` ### With `mut` From 94eb80ea48df61d2ab7525961e3604720aee819d Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Fri, 29 Mar 2019 11:43:24 -0500 Subject: [PATCH 6/8] Update text/0000-bind-helper.md Co-Authored-By: rtablada --- text/0000-bind-helper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-bind-helper.md b/text/0000-bind-helper.md index 425282a490..8226a6ac01 100644 --- a/text/0000-bind-helper.md +++ b/text/0000-bind-helper.md @@ -49,7 +49,7 @@ return function() { ```js return function() { - this.log.call(this, 1, 2); + this.add.call(this, 1, 2); } ``` From ff132ff2cec0a9f02880dbe1c8f86a790ce1488e Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Fri, 5 Apr 2019 12:49:05 -0700 Subject: [PATCH 7/8] Update to `fn` --- text/0000-bind-helper.md | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/text/0000-bind-helper.md b/text/0000-bind-helper.md index 8226a6ac01..255c3cadd9 100644 --- a/text/0000-bind-helper.md +++ b/text/0000-bind-helper.md @@ -3,11 +3,11 @@ - RFC PR: https://github.com/emberjs/rfcs/pull/470 - Tracking: (leave this empty) -# `{{with-args}}` Helper +# `{{fn}}` Helper ## Summary -This RFC introduces a new helper, `{{with-args}}`, to allow clear argument passing for functions in templates. +This RFC introduces a new helper, `{{fn}}`, to allow clear argument passing for functions in templates. ## Motivation @@ -21,18 +21,18 @@ To understand the complexity of `action` there are many complex behaviors includ At build time the `action` helper currently is passed through an AST transform to explicitly bind the `this` to be deterministic at runtime. This is a private API where the outputted Glimmer is not a 1-1 to the template. Also, the `action` helper is confused and has overlap with the `action` modifier which has similar but slightly different behavior. -Instead of this confusing and overloaded behavior, a new `with-args` helper would be introduced to do partial application (with no need for build time private APIs), and context binding will be done instead using the `@action` decorator in classes. +Instead of this confusing and overloaded behavior, a new `fn` helper would be introduced to do partial application (with no need for build time private APIs), and context binding will be done instead using the `@action` decorator in classes. ## Detailed design -The `with-args` helper will take in a function and then the set of arguments that will be partially applied to the function. +The `fn` helper will take in a function and then the set of arguments that will be partially applied to the function. -Here are some examples of the `with-args` helper and the equivalent JS: +Here are some examples of the `fn` helper and the equivalent JS: ### Simple Case On Argument Curry ```hbs -{{with-args this.log 1}} +{{fn this.log 1}} ``` ```js @@ -44,7 +44,7 @@ return function() { ### Multiple Argument Partial Application ```hbs -{{with-args this.add 1 2}} +{{fn this.add 1 2}} ``` ```js @@ -53,9 +53,7 @@ return function() { } ``` -The use of `function` application like so allows us to preserve/pass through the `this` context of the calling site accurately, so creating a function `with-args` is equivalent to the same function _without_ args. - -We will also reserve the `withArgs` helper name for future use as an alias, anticipating changes coming from template imports and most helpers/components conforming to valid JS identifiers. +The use of `function` application like so allows us to preserve/pass through the `this` context of the calling site accurately, so creating a function `fn` is equivalent to the same function _without_ args. ### Comparison to Action Helper/Modifier @@ -68,8 +66,8 @@ We will also reserve the `withArgs` helper name for future use as an alias, anti - - + + ``` ### With `mut` @@ -80,19 +78,19 @@ We will also reserve the `withArgs` helper name for future use as an alias, anti - - + + ``` ## How we teach this -For guides we would switch to recommending the `with-args` helper to pass functions into components args and modifiers. +For guides we would switch to recommending the `fn` helper to pass functions into components args and modifiers. We'll teach the helper as similar to the `hash` and `arr` helpers - `fn` returns a function with some arguments being passed to it. In guides we would no longer recommend using the `action` helper based on the reasons listed in motivations. ## Drawbacks -* `with-args` is a bit of a verbose name, and given this is a commonly used helper it could be a papercut to users and make templates harder to read. Combined with the extra complexity of requiring the `@action` decorator to bind context, and the `{{on}}` modifier to trigger events, it could lead to users avoiding the helper altogether in favor of `{{action}}`. +* `fn` is not the clearest name, and could be difficult for users to understand. ## Alternatives @@ -103,11 +101,12 @@ There are also a number of potential alternatives for names: * `args` - A shorter, simpler name with similar properties. This is somewhat less self-explanatory (on its own, without context, one might think it refers to component args, or does something with them), but may make up for this by being short and simple. * `bind` - The original name this RFC suggested. `bind` is fairly _imperative_, it describes the action that we do to the function rather than what is returned. It also does not exactly match the JS method API, and as noted in the RFC feedback this could be confusing. Finally, it requires stopping to teach the concept of binding and how that works, which is a lot of overhead for a helper that will be used early and often. * `call` - This reads nicely in templates, but is _very_ imperative and has already been confusing to folks when discussed. It differs significantly from the JS method API, teaching around this would be difficult, though possible. It also is not clear that it is _not required_ unless args are being passed, so we may see users attempting to use it for plain functions. +* `with-args` - Descriptive, but fairly verbose Names that have been considered, but passed over: * `apply` - Same downsides as `call`, but less nice to read. -* `applyArgs` - Similar enough to `with-args`, but uses more obscure computer-science-y terminology without many benefits. +* `applyArgs` - Similar enough to `fn`, but uses more obscure computer-science-y terminology without many benefits. * `partial` - From LISP and other languages. `partial` there means "Partial Application". This is a computer-science-y term that isn't super explanatory, plus `partial` is already a (deprecated) feature in Ember templates. * `papply` - From R. Generally unaesthetically pleasing, and same issues as `partial` * `action` - We considered trying to reclaim the "action" term, but it still has the same problems of overlap with the modifier and decorator, and there isn't an easy transition path to deprecating the automatic `this` binding. From bb9d7cd498ab06fb5650e4dca3bba85863ef6fbd Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Fri, 12 Apr 2019 14:52:29 -0400 Subject: [PATCH 8/8] Update the filename. --- text/{0000-bind-helper.md => 0470-fn-helper.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-bind-helper.md => 0470-fn-helper.md} (100%) diff --git a/text/0000-bind-helper.md b/text/0470-fn-helper.md similarity index 100% rename from text/0000-bind-helper.md rename to text/0470-fn-helper.md