Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

`{{fn}}` Helper #470

Merged
merged 8 commits into from Apr 12, 2019

Conversation

@rtablada
Copy link
Contributor

rtablada commented Mar 21, 2019

rendered

rtablada added some commits Mar 21, 2019

@rtablada

This comment has been minimized.

Copy link
Contributor Author

rtablada commented Mar 21, 2019

A possible speedbump for this is the addon https://github.com/Serabe/ember-bind-helper.

This addon provides an incompatible API to this RFC and is implemented by performing an AST transform to the private API of the action helper and modifier. to the target named attribute with the action helper

The API for this RFC provides a clearer and cleaner mental model and a "just code" implementation, but we should understand that there is potential collisions for teams that are using this addon in their apps today.

@Serabe

This comment has been minimized.

Copy link
Member

Serabe commented Mar 21, 2019

I'm not using any private API.

I've already talked to some users of the addon about providing an upgrade path to this RFC.

@jessica-jordan jessica-jordan referenced this pull request Mar 21, 2019

Merged

The Ember Times No. 90 - March 22nd 2019 #21

7 of 13 tasks complete
```

```js
this.car.logout.bind(this.car, 1000, 'mile')

This comment has been minimized.

Copy link
@Serabe

Serabe Mar 21, 2019

Member

This should be this.car.logout.bind(this.honda, 1000, 'mile').

@Serabe

This comment has been minimized.

Copy link
Member

Serabe commented Mar 21, 2019

I'm very much in favor of this landing. I'll try to get a beta version of ember-bind-helper that facilitates current users stop using it.

this.session.logout.bind(this.session)
```

### Setting the `this` context and arguments

This comment has been minimized.

Copy link
@chrisrng

chrisrng Mar 22, 2019

imo this is a little confusing since its the opposite of what the JavaScript bind function argument order is (wrt the context)

Can we make use of the array helper instead? Similar to how Angle Bracket does positional params (https://guides.emberjs.com/release/reference/syntax-conversion-guide/#toc_params-array)

{{bind this.car.drive context=this.honda params=(array 1000 'mile')}}

This comment has been minimized.

Copy link
@Serabe

Serabe Mar 22, 2019

Member

Doing this would be making the use of this helper too verbose and a step backwards with respect to the action helper.

This comment has been minimized.

Copy link
@pzuraq

pzuraq Mar 22, 2019

Contributor

Yeah, we debated about this a bit, it comes down to “naming is hard”. What we want is a partial application helper. We’ve discussed:

  • partial - used by aome other languages, but we can’t because it means something in hbs
  • apply - also used by other languages, but already used in JS, and means something very different
  • curry - could work, but then we have to explain the term. Also, may not be technically correct (currying produces a function that recieves exactly 1 argument, or something like that)
  • papply - easy to misinterpet if you don’t know what the term “partial application”
  • partial-apply - fairly verbose for a common task

bind is the least bad choice IMO, it’s like if you made a bind function with a slightly better API, but I also agree it’s not ideal and could be confusing. I think curry is the second best out of those options, and am open to suggestions!

This comment has been minimized.

Copy link
@luxferresum

luxferresum Mar 22, 2019

but isnt is confusing considered that in 90% of the cases {{bind will not be used to bind context but @action will bind context?

This comment has been minimized.

Copy link
@viniciussbs

viniciussbs Mar 24, 2019

I got confused by that too, @luxferresum. If @action will be the recommended way to bind context, I prefer a helper called curry, apply-args or something on that direction to partially apply arguments.

Show resolved Hide resolved text/0000-bind-helper.md Outdated

## 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).

This comment has been minimized.

Copy link
@mehulkar

mehulkar Mar 22, 2019

s/junior developers/new ember developers. IMO this context binding seems like a basic thing a framework should provide when template/JS class are already closely coupled. it's confusing if it's not out-of-box behavior.

This comment has been minimized.

Copy link
@Serabe

Serabe Mar 22, 2019

Member

👍 to s/junior developers/new ember developers/

While it might look like a good idea to have automagic context binding, this behaviour is closes to how JS and other libs work. Reducing the difference between JS and HBS is a great goal, IMHO.

This comment has been minimized.

Copy link
@jonnii

jonnii Mar 22, 2019

When calling an action on a component don't you almost always want this to be the component instance? would an @action called with bind be automatically have the this context set to the component instance?

This comment has been minimized.

Copy link
@pzuraq

pzuraq Mar 22, 2019

Contributor

Correct, @action provides context binding. The magic that we’re discussing that is problematic is the fact that a helper or modifier can access this context implicitly, in order to bind it.

This comment has been minimized.

Copy link
@jonnii

jonnii Mar 22, 2019

In which case the "how we teach this" section is misleading:

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.

This suggests you'd always want to use bind over action but I'd think that bind is only useful for the cases where you'd want a different context, which is (in my experience) never been a problem I've faced. Not to say that it's not a problem, but targetObject was how this was done in the past and that wasn't very pleasant.

This comment has been minimized.

Copy link
@mehulkar

mehulkar Mar 23, 2019

@rtablada, A little confused, what is the purpose of the default target=this being applied at build time?

@viniciussbs Yeah, actions were a weird idiosyncrasy of the framework. bringing in plain JS bind, but keeping the idiosyncrasy makes things more weird for me, and just seems like an unnecessary hat tip to backwards compatibility. Wouldn’t it make more sense to just default to context=this?

This comment has been minimized.

Copy link
@Serabe

Serabe Mar 23, 2019

Member

@mehulkar defaulting to context=this is more harmful than helpful. You might not be passing a function defined in the template context, but one coming from a service or a model, like {{bind this.model.save}}. That is the reason in ember-bind-helper we default to the everything but the last part of the call invocation (in the example this.model).

This comment has been minimized.

Copy link
@jonnii

jonnii Mar 23, 2019

When using bind with a context and an @action decorator which one wins?

This comment has been minimized.

Copy link
@Serabe

Serabe Mar 23, 2019

Member

@action

This comment has been minimized.

Copy link
@mehulkar

mehulkar Mar 23, 2019

That is the reason in ember-bind-helper we default to the everything but the last part of the call invocation

@Serabe yeah this would be fine (and better) also 👍 . Defaulting to null is what I'm objecting to.

@rtablada rtablada referenced this pull request Mar 22, 2019

Merged

`{{on}}` Modifier #471

### Setting the `this` context

```hbs
{{{bind this.session.logout context=this.anotherSession}}}

This comment has been minimized.

Copy link
@luxferresum

luxferresum Mar 22, 2019

I hope the tiple braces are by accident?

### Setting the `this` context

```hbs
{{{bind this.session.logout context=this.anotherSession}}}

This comment has been minimized.

Copy link
@bendemboski

bendemboski Mar 23, 2019

I think context is a pretty overloaded word that doesn't really convey a lot of meaning. Why don't we use thisArg to make it less ambiguous and match the EMCA spec's naming?

This comment has been minimized.

Copy link
@luxferresum

luxferresum Mar 23, 2019

+1, or just this?

@viniciussbs

This comment has been minimized.

Copy link

viniciussbs commented Mar 23, 2019

I use ember-bind-helper today to call service methods. Using the proposed bind template helper and something like a @bound decorator, I could use the same sintaxe I already use, I guess:

@bound
method() {
  // method on a service
}
<button onclick={{bind myService.method}}>
  Call a method on a service
</button>
@Serabe

This comment has been minimized.

Copy link
Member

Serabe commented Mar 23, 2019

@viniciussbs in that case you don't even need the bind helper, as you are not passing any extra arguments.

@rtablada

This comment has been minimized.

Copy link
Contributor Author

rtablada commented Mar 23, 2019

@viniciussbs for some clarification we would recommend @action in the service and then using the on modifier on the button (no bind needed since you are not partially applying args or setting this).

@action
method() {
  // method on a service
}
<button {{on "click" this.myService.method}}>
  Call a method on a service
</button>
@Serabe

This comment has been minimized.

Copy link
Member

Serabe commented Mar 23, 2019

Quick update: if you are using ember-bind-helper, see the deprecation path and let me know if I can do something else to ease it.

@viniciussbs

This comment has been minimized.

Copy link

viniciussbs commented Mar 24, 2019

@rtablada So, if using @action inside a service is OK, the bind helper will be - most of the time - used to partially apply args. Like @luxferresum said, bind will not be used to bind context; we will bind context with @action and partially apply args with bind. That sounds weird and makes like more the other names shared by @pzuraq. I'll comment there.

@jonnii

This comment has been minimized.

Copy link

jonnii commented Mar 24, 2019

@rtablada Like @luxferresum said, bind will not be used to bind context; we will bind context with @action and partially apply args with bind.

Except when you supply your own context - this means you need to look at both the template and the code to understand what the context of the ultimate action is. Is there a solution where the context is always defined in one place or the other?

@MelSumner MelSumner referenced this pull request Mar 26, 2019

Open

Octane Tracking Issue #17234

74 of 134 tasks complete
@pzuraq

This comment has been minimized.

Copy link
Contributor

pzuraq commented Mar 27, 2019

We discussed this RFC at the face-to-face last week, and with @rtablada's permission I've updated it to reflect the feedback from that meeting and the current state of things. Our main feeling is that the mismatch between the semantics of bind in JS and templates would be confusing to users (and there was some feedback to that effect), so it would be better to not have context binding at all for this helper, relying entirely on the @action decorator for binding.

We discussed many, many names for this new helper. I outlined some of the alternatives we went over in the RFC, along with the reasoning and pros/cons for them. There is a decent amount of support for with-args:

  • It's declarative, tells us what the value it returns is (the function with these arguments attached)
  • It's simple and clear, doesn't require an understanding of the terms "partial application" or "currying" to get.
  • It can generally be read and understood without any context, since it's descriptive and simple.

Overall there was more support for it than bind, which is why I updated the RFC to use it, but I definitely want to be clear that we are still open to suggestions! If anyone has ideas for the helper name, please feel free to comment and add them here 😄

@pzuraq pzuraq changed the title Bind Helper `{{with-args}}` Helper Mar 27, 2019

@jonnii

This comment has been minimized.

Copy link

jonnii commented Mar 28, 2019

@pzuraq the examples still have the context argument, is this intentional? One more question. Would something like this be possible?

<button onclick={{this.increment()}}>Click</button>
<button {{on "click" this.add(1, 2)}}>Click</button>

This is such a common operation that maybe there's a way to express it with a more "native" feeling syntax. Under the covers this would be compiled to use with-args.

@pzuraq

This comment has been minimized.

Copy link
Contributor

pzuraq commented Mar 28, 2019

@jonnii thanks for catching those! That was unintentional, I'll update soon.

So, the thing is, you examples will be possible soon (if they aren't already). They just have a bit of a different syntax:

<button onclick={{this.increment}}>Click</button>
<button {{on "click" (this.add 1 2)}}>Click</button>

Glimmer templates have their own way of invoking functions (helpers), which is different from JavaScript. I don't think it makes much sense for us to try to embed JS in our templates, that would definitely be going the JSX route.

The issue is though, the above runs those helpers immediately, because it's an inline function call. What we want is to have a syntax where we return a new function that is called later. In React/JSX they have this same problem, and they encourage arrow functions in templates:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>

We briefly discussed adding something like arrow functions to Glimmer templates, something like:

<button {{on "click" (|e| => (this.add 1 2))}}>Click</button>

But you can see this is actually still pretty verbose, and it opens up a whole can of worms. I think it's a feature we can definitely consider in the future, but in the meantime it makes more sense to have a helper which passes back a bound function.

Show resolved Hide resolved text/0000-bind-helper.md Outdated

@jessica-jordan jessica-jordan referenced this pull request Mar 28, 2019

Merged

The Ember Times No. 91 - March 30th 2019 #29

8 of 22 tasks complete
Update text/0000-bind-helper.md
Co-Authored-By: rtablada <ryan.tablada@gmail.com>
@luxferresum

This comment has been minimized.

Copy link

luxferresum commented Mar 29, 2019

I'm also very happy with this change. The only thing thats not so elegant is that with-args is a bit a long name. Have we considered just args as the name?

@rtablada

This comment has been minimized.

Copy link
Contributor Author

rtablada commented Mar 29, 2019

@luxferresum yeah there's been a bunch of bike shedding around name. I've come around to with-args since the pros outweigh the cons

Pros:

  1. Descriptive
  2. Opens up args to be used for other helpers

Cons:

  1. with-args is a bit long to type

With template/sfc imports in the future pipeline you'll be able to alias to match your teams naming schemes.

@richard-viney

This comment has been minimized.

Copy link

richard-viney commented Mar 29, 2019

Would the name args possibly be a point of confusion due to the existence of this.args on the component?

@amyrlam amyrlam referenced this pull request Apr 1, 2019

Merged

The Ember Times No. 92 - April 5th 2019 #46

11 of 20 tasks complete
@pzuraq

This comment has been minimized.

Copy link
Contributor

pzuraq commented Apr 3, 2019

After more discussion, a few more options have come up for us to consider:

  • fn, short for "function". There is prior art here from Lisp dialects which use fn for declaring an anonymous function, we would be using it for a similar purpose, but slightly different. It's much less verbose than with-args, which is a plus, but may be a little less clear:
<div {{on "click" (fn this.save model)}}></div>
  • A new custom syntax for partial application, something like {{> }} or {{=> }}. This could be nice for brevity, but may be less searchable and clear for newcomers, and may be more difficult to learn in general:
<div {{on "click" (> this.save model)}}></div>

{{> }} is also reserved in Handlebars templates, but is not currently valid syntax in Ember templates, so it could still be used. Using this syntax in particular would cause us to diverge from Handlebars moreso than we already have.

@balinterdi

This comment has been minimized.

Copy link

balinterdi commented Apr 4, 2019

I was at peace with with-args but I think fn is even better as it more accurately describes what it does (since the first argument to the helper is the function itself, only the other arguments are the arguments to that function).

> and other symbols are just too cryptic, it makes teaching/learning harder for no good reason. I also don't mind typing, so func or even function are fine, too, but fn is easy to search for, so I think it's perfect.

@chriskrycho

This comment has been minimized.

Copy link
Contributor

chriskrycho commented Apr 4, 2019

Alignment with Rust can only be a good thing. We've got ::, now fn? I'M SO IN.

😆

@pzuraq pzuraq changed the title `{{with-args}}` Helper `{{fn}}` Helper Apr 5, 2019

@rwjblue

This comment has been minimized.

Copy link
Member

rwjblue commented Apr 5, 2019

The RFC has been updated to use fn (instead of {{with-args}}), and with that change the core team believes this is ready to be moved into the final comment period.

@viniciussbs

This comment has been minimized.

Copy link

viniciussbs commented Apr 5, 2019

I still prefer with-args. I don't think it's too verbose. And verbosity is not a problem for me when it's for clarity sake - like the did-insert-element element modifier. But fn is a good alternative to bind, too.

@cibernox

This comment has been minimized.

Copy link
Contributor

cibernox commented Apr 6, 2019

Fwiw, I also prefer bind or with-args. I don't find the former too imperative, but I do find fn a bit cryptic.

@bendemboski

This comment has been minimized.

Copy link

bendemboski commented Apr 6, 2019

I still prefer curry because, according to it's common (not mathematical) usage, that's exactly what the helper does. But maybe that's not a widely-used/understood enough term?

@chriskrycho

This comment has been minimized.

Copy link
Contributor

chriskrycho commented Apr 6, 2019

@bendemboski heh, well, strictly speaking, it partially applies a function rather than currying it. And (at least in my view) we probably shouldn't encourage the confusion of the two terms, as they are both independently useful concepts!

Edit: clarification for those who might not be familiar with the terms—

Partial application takes a function which takes m arguments and applies the n arguments you might supply to it and returns a function that expects m - n arguments. So, using lodash's _.partial method for example:

function addFourNumbers(a, b, c, d) {
  return a + b + c + d;
}
let addTwoNumbers = _.partial(addFourNumbers, [1, 2]);

Here addTwoNumbers is a function which takes two arguments; it's equivalent to this:

function addTwoNumbers(c, d) {
  return addFourNumbers(1, 2, c, d);
}

Currying takes a function which takes m arguments and turns it into a series of m functions which take one argument each. So, again using lodash, this time with _.curry:

function addFourNumbers(a, b, c, d) {
  return a + b + c +d;
}

let curriedAddFourNumbers = _.curry(addFourNumbers, 4);

This is equivalent to this:

let curriedAddFourNumbers = a => b => c => d => a + b + c + d;

You can see how these two things are useful together (and a number of languages automatically curry their functions as a powerful complement to partial application, so people often associate them), but the differences between them are useful to understand.

@bendemboski

This comment has been minimized.

Copy link

bendemboski commented Apr 6, 2019

@chriskrycho yeah, that's a fair point. I was sorta relying on the fact that I believe most people kinda blur or confuse the two notions, so calling it currying would immediately invoke the idea of partial application, and since there's no sensible way (that I can think of) that {{curry this.doSomething this.model}} could describe a currying operation, it would end up being pretty intuitive. But maybe relying on a general lack of clarity around a distinction between terms isn't a good way to go...

@chriskrycho

This comment has been minimized.

Copy link
Contributor

chriskrycho commented Apr 6, 2019

@bendemboski yeah, I actually can think of ways that you might use it, but they're used together often enough to cause exactly that confusion. (I edited in an example for folks that are following along via email, which should hopefully show why they're often used together and why they're worth keeping conceptually distinct!) I definitely think keeping it correct is a worthwhile goal: it's almost never a good idea to intentionally name something in a way that runs contrary to established conventions or definitions.

return function() {
this.add.call(this, 1, 2);
}
```

This comment has been minimized.

Copy link
@simonihmig

simonihmig Apr 8, 2019

Contributor

I got a bit confused by looking at these examples, as they make it look like you have to pass all expected arguments, rather than partially applying them. Wouldn't the correct JS equivalent of {{fn this.add 1 2}} look more like this?

return function(...args) {
  this.add.call(this, 1, 2, ...args);
}

This comment has been minimized.

Copy link
@pzuraq

pzuraq Apr 8, 2019

Contributor

Yeah actually that’s totally correct, we will update them!

@TRMW

This comment has been minimized.

Copy link

TRMW commented Apr 8, 2019

Another vote for with-args over fn here, since it makes it a little clearer what this helper does. Either way I think these changes will be way less confusing than action, which I've seen many new Ember devs get tripped up by.

@sheriffderek

This comment has been minimized.

Copy link

sheriffderek commented Apr 9, 2019

Great discourse! Maybe we can ask a non-ember person

what they think {{on "click" (fn (mut showModal) true)}} might mean.

fn seems like a pretty good place to land / but things like mut were always a big turn off to me - and I'd be curious how terrifying this looks to someone deciding to use the framework for the first time.

@pzuraq

This comment has been minimized.

Copy link
Contributor

pzuraq commented Apr 9, 2019

@sheriffderek I think these examples are for folks who do use mut. We don't really recommend it directly in the guides, and it's not going to be more (or less) recommended as a result of this RFC. It's a different helper that can be combined with this helper, so it's a bit of a different concern.

@rwjblue rwjblue merged commit f1d86f9 into emberjs:master Apr 12, 2019

1 check was pending

continuous-integration/travis-ci/pr The Travis CI build is in progress
Details

@rwjblue rwjblue referenced this pull request Apr 12, 2019

Open

RFC #0470 - Tracking for {{fn}} Helper #33

1 of 4 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.