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

Unify @tracked and @action export paths #547

Closed
pzuraq opened this issue Oct 4, 2019 · 11 comments
Closed

Unify @tracked and @action export paths #547

pzuraq opened this issue Oct 4, 2019 · 11 comments

Comments

@pzuraq
Copy link
Contributor

pzuraq commented Oct 4, 2019

One of the more common pieces of feedback we've been receiving so far with the Octane preview is that it's fairly burdensome to write out all of the import paths when writing a simple component:

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

The decisions for these import paths were made carefully and with good reason, but ultimately it's proving fairly unergonomic, and it may make sense to begin re-exporting some of them from a shared location.

We could re-export @tracked and @action from @glimmer/component, but they are also much more general decorators - they can be applied to Controllers, Services, Helpers, etc. We would either have to re-export from each of these for consistency, or force users to learn two potential import paths.

Alternatively, we could group these two decorators, since they are usually applied together anyways. Some possibilities include:

import { action, tracked } from '@ember/object';
import { action, tracked } from '@ember/class-utils';
import { action, tracked } from '@ember/utils';
import { action, tracked } from '@ember/decorators';

import { action, tracked } from '@glimmer/object';
import { action, tracked } from '@glimmer/class-utils';
import { action, tracked } from '@glimmer/utils';
import { action, tracked } from '@glimmer/decorators';

The first decision would be whether to group them in an @ember namespace, or a @glimmer namespace. The original decision for import { tracked } from '@glimmer/tracking' was that it would allow us to support interop between stand-alone Glimmer and Ember, so choosing an @glimmer namespace would help with this goal.

However, it would also introduce @action to Glimmer.js, and this may not be an ideal name for the function-binding decorator. The term "action" has a lot of historical baggage in Ember, and doing this would introduce that baggage to Glimmer. If we aren't satisfied with the name @action, re-exporting from an @ember namespace may make more sense. Either way, Glimmer.js will be needing to add a function-binding decorator in the near future, either @action or some other name.

@buschtoens
Copy link
Contributor

With IDEs that support automated imports, e.g. VS Code, when the typings for Ember packages are installed, this becomes a non-issue. Inversely, adding additional re-exports makes it slightly more complicated / annoying since you now have multiple places to chose from, adding some cognitive load.

If we change the import paths, I'd recommend to drop @action in favor of just @bind, to shed the historical baggage and legacy interoperability code.


Tangentially related #273 (comment): Renaming / Re-exporting inject as service to allow for more ergonomic automated imports in IDEs.

import { service } from '@ember/service';

@pzuraq
Copy link
Contributor Author

pzuraq commented Oct 5, 2019

With IDEs that support automated imports, e.g. VS Code, when the typings for Ember packages are installed, this becomes a non-issue.

I'm not sure we're at the point yet where we can just accept IDE tooling as a solution across the board, though maybe we are. This also doesn't work for documentation and example code, which become much more bloated by always having to include a large list of imports, so anyone writing a blog post or trying to demo Ember will have a bit of extra burden.

I do think the argument that this could harm IDE tooling is a good one though. There are good reasons for @tracked to exist in @glimmer/tracking, so I don't expect us to change the canonical import path. Is there a way to let autocomplete in IDEs know which import path should be preferred?

If we change the import paths, I'd recommend to drop @action in favor of just @bind.

There is disagreement on the terminology of @bound or @bind, some find that the term is too "in the weeds" and believe that it ends up being more confusing since you have to then explain what a bound function is. I don't believe we would be able to get consensus on one of those terms in the near term (though this may change if TC39 decides to ship it as a built-in, but that is a ways off).

@callback has been floated as an alternative. @action is also a significantly less overloaded term in Octane, so it may be that the historical baggage will naturally fall out over time, and "actions" will become a synonym for bound functions that are easier to explain:

Q: What is an action?
A: It's a function that you can use in Ember templates.

Q: But I can use other functions?
A: Yes, but there may be bugs if you don't use @action

Definitely open to other ideas as well!

@gossi
Copy link

gossi commented Oct 5, 2019

I'm not sure we're at the point yet where we can just accept IDE tooling as a solution across the board, though maybe we are. This also doesn't work for documentation and example code, which become much more bloated by always having to include a large list of imports, so anyone writing a blog post or trying to demo Ember will have a bit of extra burden.

I don't think any of these arguments should have an impact from where the named functions should be imported from. Find a home for them to where they belong and feel comfortable. Rest of it, ie IDE tooling will be the nice sugar to make that more ergonomic to us.

Personally for me @tracked belongs to glimmer (might be because it was always there). Yet, I would find the solution more towards: Can glimmer be shipped with it and works? Is it only required for ember? If I want to start a glimmer project but need to install something from ember, that I'd say hints something is wrong.

@pzuraq
Copy link
Contributor Author

pzuraq commented Oct 5, 2019

Glimmer will require a @bound/@callback/@action in the near future, though it won't have the same requirements of interop with the older {{action}} helper and actions hash.

@chriskrycho
Copy link
Contributor

chriskrycho commented Oct 5, 2019

Is there a way to let autocomplete in IDEs know which import path should be preferred?

Unfortunately, the answer here is basically “no”. VS Code uses some heuristics based on recency (and maybe some other things! Not sure!) to determine what to import, but if you have multiple imports for a name available, all the major editors and IDEs will usually show you all of them.

There is disagreement on the terminology of @bound or @bind, some find that the term is too "in the weeds" and believe that it ends up being more confusing since you have to then explain what a bound function is. I don't believe we would be able to get consensus on one of those terms in the near term (though this may change if TC39 decides to ship it as a built-in, but that is a ways off).

@callback has been floated as an alternative. @action is also a significantly less overloaded term in Octane, so it may be that the historical baggage will naturally fall out over time, and "actions" will become a synonym for bound functions that are easier to explain…

I understand this objection, but I disagree with it and I’d like to take this opportunity to put in writing why. It is true that in general we want APIs to describe their intent—i.e. to make a given class method safe to pass around through templates (and elsewhere). @bound doesn’t do that perfectly. (@callback also doesn’t do that, and worse is actively misleading: not all callbacks need to be bound!) In the category of things that are kind of roughly descriptive of the purpose of the API, @action is probably as good as anything.

It is also true that explaining function binding can get pretty hairy, especially for developers new to JavaScript. The fact that you can rebind what this is is a surprising and quirky corner of the language!

However, even granting those things, I think that attempting to paper over this or hide it is misdirected, however well-intended. First, it adds a layer of Emberism that’s the opposite of the direction we’ve been moving during the lead-up to Octane in general. Second, it misses an important pedagogical opportunity.

I think that first point is fairly obvious, so I won’t belabor it. The second, though, warrants a bit of tracing out. Pedagogically, we want to minimize bumps as someone is coming up to speed on the framework, especially for someone just getting started with JS or even with programming in general. However, some bumps are actually useful when teaching, and I think this is one of them!

Here’s why: the immediate question for anyone who wants to understand what’s happening when they see that we recommend decorating the function this way—i.e. any experienced JS developer, or any developer who isn’t happy just trusting things to magically work—is: “Why does this need to be decorated with @<whateverNameWeChoose>?” And the answer… requires you to explain why function binding is important in the context of passing functions around through templates.

@action or @templateSafe or whatever is an attempt to name it so that you can just skip past it when starting. As in the example @pzuraq gave:

Q: What is an action?
A: It's a function that you can use in Ember templates.

Q: But I can use other functions?
A: Yes, but there may be bugs if you don't use @action

Substitute in @bound here and… it works pretty similarly.

Q: What is @bound for?
A: It makes a class method safe to use in Ember templates.

Q: Can I use class methods without @bound?
A: Yes, but there may be bugs if you don't use it!

Either way, you can say “You don’t have to worry about the details to get started,” in other words. But there are other tradeoffs after you get past that (very helpful) hand-wave.

@action papers over the mechanics a bit more. It’s a nicer name. But the natural explanation here—“It’s a function you can use in Ember templates”—not only obscures what it’s actually doing and why (which may be fine for the early phase of learning), but is actually actively misleading. After all: helpers are functions, and they don’t require being marked this way, and they’re perfectly safe to use in templates. (And as helper managers hopefully land in the future and we move to the ability to define them in local scope, that contrast only becomes sharper.) You really need to say quite specifically that it’s a class method that you can use in Ember templates.

Regardless of what it’s named, you end up saying something like “this makes class methods safe to use in templates, and you can worry about the details of why later.” But there is one very important difference: @bound says exactly how it makes it safe to use in a template. For experienced JS developers, this doesn’t even require explanation at that point: “Oh, this is binding the context, yep, makes sense!” For inexperienced JS devs, it’s a pointer at what they need to understand for the reason to make sense, and it makes the explanation (in advanced guides, API docs, etc.) straightforward: @bound binds the context of the class to the method.

You don’t need to understand function binding to be productive in JS right away. But you do need to understand it eventually. It seems to me that it’s extremely valuable to use a name that matches the actual thing you need to understand, which matches the base JS APIs on which it’s built (.bind()), which matches all the other JS documentation out there which is not specific to Ember, which immediately provides the right mental model for anyone coming in with existing JS experience, and which provides the right starting point for questions for anyone who comes in without that experience.

Appendix – Search Results

Notice that Ember-specific knowledge is required for action or similar. For bound, just searching "JavaScript bound" gets you to an explanation!

@jrjohnson
Copy link

I tripped over these imports immediately with two issues:

  1. Too many imports. I messed this up several times because I assumed I wouldn't need to import from so many places and that docs were just in transition. This probably won't bother me so much once I'm using the ember-cli generators and not playing with glimmer components as a hobby, but it seemed strange to me immediately.

  2. I really didn't understand that what action was doing was, in fact, binding so I didn't use it initially where I should have and needed the debugger to clear it up. This is probably curse of knowledge stuff but I think calling it bind would have been way clearer.

I'm not sure I buy that action which could mean anything is easier to use than bind which means something specific. In both cases you have to explain what is going on eventually, it seems simpler to rely on language level concepts then ember specific wrappers.

With that being said, I'd vote for import { bind } from '@ember/object'; as binding makes tons of sense in the object namespace, where action doesn't.

@dfreeman
Copy link
Contributor

dfreeman commented Oct 7, 2019

Independent of the name (though @chriskrycho's reasoning lines up pretty well with my thoughts), one thing that does jump out at me about @action is that everything currently in the @ember/object package is tied to EmberObject and computed properties, both of which are downplayed in the Octane model.

Given that @action (or @bound or whatever) is an important piece of Octane, homing it there feels like it might be a little odd to me.

@pzuraq
Copy link
Contributor Author

pzuraq commented Oct 7, 2019

I think that’s a bit inaccurate, for instance @computed is imported from @ember/object, but it actually has nothing to do with EmberObject and can be used independently of it. I think of @ember/object as the object model package, which includes general utils that all objects in an Ember app may have to use.

I get how that could be confusing, but I think it’ll be clearer once EmberObject is deprecated and we can write apps without it. The same reasoning works for @glimmer/object, which wouldn’t have the same baggage. We could be more explicit though as well, something like @glimmer/object-utils.

@dfreeman
Copy link
Contributor

dfreeman commented Oct 7, 2019

for instance @computed is imported from @ember/object, but it actually has nothing to do with EmberObject and can be used independently of it

That's why I said "EmberObject and computed properties" 😉

I know the two are usable independent of one another, but they're both intended not to be a significant part of the Octane programming model and are eventually slated to be phased out, aren't they? Are there things other than action in that package we foresee outlasting that eventual removal?

@pzuraq
Copy link
Contributor Author

pzuraq commented Oct 7, 2019

That's why I said "EmberObject and computed properties" 😉

Ah, missed that part, my bad.

Are there things other than action in that package we foresee outlasting that eventual removal?

I believe that get and set will be around for quite some time, if only for compatibility, along with notifyPropertyChange. I do believe most of it will be going away though, which is why personally I'd prefer to put them in @glimmer/object, and start off a fresh package for general object-related utilities.

@wagenet
Copy link
Member

wagenet commented Jul 22, 2022

I'm closing this due to inactivity. This doesn't mean that the idea presented here is invalid, but that, unfortunately, nobody has taken the effort to spearhead it and bring it to completion. Please feel free to advocate for it if you believe that this is still worth pursuing. Thanks!

@wagenet wagenet closed this as completed Jul 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants