-
-
Notifications
You must be signed in to change notification settings - Fork 25
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
Possibly rethink modifiers #7
Comments
I would consider the stackable API to be more aligned what decorators (or annotations in java) are. Give how |
Yeah, I also lean more towards the stackable API for the very same reasons. It just feels more in line with the existing ecosystem. We could clear the first concern (
I don't know how feasible the last option is, but I think it'd be really sexy, not only for this case, but also in general, especially with class ExampleService extends Service {
@on('init')
@keepLatest
@task
*loadStuff() {
// ...
}
async someMethod() {
const value = await this.loadStuff();
console.log(this.loadStuff.last.value === value);
}
} |
👍
I'd love that, but the last time that was investigated, I believe the conclusion was that it would make supporting all the nice derived state difficult. |
I'm having trouble parsing this. Can you add an example of the "current implementation" with multiple decorators? (Sorry, in general I'm super rusty on decorators APIs and I don't personally use decorators or this repo) |
Looking at annotation systems in other languages, and given there will likely be quite a few decorators working their way into JS and Ember in the near future, I’m not as sure about the stackable api. The main issue occurs when you start mixing two such systems together, for whatever reason. We’ve had this happen in our Java frameworks in particular and it can get quite noisy very quickly. That said, Java also uses annotations for some pretty hacky things (e.g. |
Sure! 😊 So right now we have these decorators, which would be equal to the following proposed decorators:
|
Are you talking about name clashes here? I share this concern. Or do you mean that, coming back to old code, you might not immediately see, which decorator belongs to which library?
Do you worry about using to many decorators in general or "the size of the stack" with the stackable API? |
@restartable
@maxConcurrency(5)
@on('click')
@cancelOn('keydown')
@debug
@task
*someTask() {
// ...
} This just looks absurd imo. But so does @task.restartable.maxConcurrency(5).on('click').cancelOn('keydown').debug
*someTask() {
// ...
} Wrapping lines might help a bit, because you have some sort of hierarchy through indentation: @task
.restartable
.maxConcurrency(5)
.on('click')
.cancelOn('keydown')
.debug
*someTask() {
// ...
} Granted, this is a really extreme example. However, thinking about all of your comments (thanks a lot, btw!), maybe a combination of the current approach and a one or both proposals might be best. For example, we could keep the "base" modifiers as joint decorators. Meaning for
And the same for
A benefit of this is that we create no API churn for existing code bases already using this addon. And, if I'm not missing anything, these are left:
For those we can think about either exporting (some of) them as their own decorators or making (some of) them available as chainable API. Some random thoughts: Since tasks that are part of a @taskGroup someTaskGroup;
@taskGroup('someTaskGroup')
*someTask() {}
@keepLatestTask.debug
*someTask() {} So now only
are left. If we rename So with these changes in mind, the above example could look like: export default class ComposableExampleComponent extends Component {
@performOn('click')
@cancelOn('keydown')
@evented
@maxConcurrency(5)
@restartableTask.debug
*someTask() {
// ...
}
@maxConcurrency(5)
@restartableTaskGroup
choreGroup;
@taskGroup('choreGroup')
*choreTask() {
// ...
}
} |
Bummer. When I can find some time, I'll do a deep dive down the rabbit hole and try to understand how the logic currently works and what might change. :)
Hm, could be. 🤔 But aren't properties on functions the equivalent to "static" properties (methods) on classes? class FooClass {
static staticMethod() {}
}
// is the same as
function FooClass() {}
FooClass.staticMethod = function() {}; |
Having too many decorators in general. If we have two or three such stackable systems being applied to a single method, it could get confusing very quickly. That said, unsure how much overlap there can be. For instance, the Argument decorators have almost zero overlap with tasks, so it would bw unlikely to see: export default class ComposableExampleComponent extends Component {
@argument
@performOn('click')
@cancelOn('keydown')
@evented
@maxConcurrency(5)
@required
@type(‘string’)
doThing() { }
} But you can see how it gets hard to parse, especially if users mix the decorators. The chaining method at least gives most modifiers context in complex systems like this. Another option here could be an options hash. This was something that was probably less natural in the pre-decorators world, but works pretty well now: @task({
maxConcurrency: 5,
performOn: ‘click’,
cancelOn: ‘keydown’,
debug: true
})
*someFunc() {} |
I also cannot come up with a scenario where you'd wanna mix task decorators with other decorators. But yeah, you never know what the future holds. 🤷♂️
I can see myself using this. Looks decent. 😀 Do you think we should keep the joint decorators ( @restartableTask({
maxConcurrency: 5
})
*someFunc() {}
// vs
@task({
modifier: 'restartable',
maxConcurrency: 5
})
*someFunc() {}
// vs
@task({
restartable: true,
maxConcurrency: 5
})
*someFunc() {} |
I like the idea of multiple decorators for the most common combinations (e.g. |
They are! I'm honestly not sure 😄 — I was essentially parrotting back something I read a very long time ago that may no longer be true or that I may have misunderstood in the first place. |
I rather like the idea of the hash of options, but with some high-level wrappers around them for common tasks. It's roughly the same approach I took with the (currently undocumented and not ready for public consumption!) @olo/principled-forms The way it works there is: we have a It works really nicely, and I think doing the same here with decorators is 💯. |
Seeing that most seem to like the options hash with high-level wrappers, I think we can go ahead implementing it. One huge benefit of it being that we create zero API churn for existing users. ❤️ |
Thanks a lot everybody for sharing your ideas and thoughts! I've implemented it and merged it to master. If you want, you can already test it as
I'd like to wait with an official release until #10 is solved. |
Taking #6 (
taskGroup
) and #3 (maxConcurrency
) into consideration, we might want to rethink how we apply modifiers. The current implementation of creating joint decorators for creating a task and applying a modifier has the advantage of being terse, but at the cost of reduced composability.I have two ideas for different solutions. Possibly a mix of both might be best.
I'm intentionally taking this to ridiculous lengths here to demonstrate potential ugliness.
Chaining API
Composable / Stackable decorators API
Considerations for the composable API:
@on
name clash with: Add @observes, @unobserves, @on, and @off decorators ember-decorators/ember-decorators#232. Would be cool, if one / our decorator could support both usages maybe.@task
and@taskGroup
(or@group
) would always need to come last. Maybe we can make the order optional or even make@task
implicit, so kinda like in the current implementation.Excited for you thoughts! 😄
The text was updated successfully, but these errors were encountered: