-
Notifications
You must be signed in to change notification settings - Fork 106
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
@deco before or after export keyword? #69
Comments
My preference (and the current TypeScript implementation) is before export
@dec1
@dec2({
option1: "foo"
})
class C {
}
// vs
@dec1
@dec2({
option1: "foo"
})
export class C {
} |
I suppose "both" might be an acceptable mix though: @dec1
@dec2({
option1: "foo"
})
export @sealed class C {
} |
The current proposal only permits decorators after This might be a change vs previous versions, which should be documented as part of #50 . |
I recall discussions with @wycats over the past two years where we discussed this and I had thought we had settled on "before One of the other reasons for TypeScript's implementation is that we have other possible keywords that can come before export
@decorator1
@decorator2
abstract class C {
} With multi-line decorators you can end up losing track of the |
The only other part of the conversation I recall was whether we wanted to eventually allow users to decorate an I had suggested an extension to the syntax to support this in a way similar to how C# attributes allow you to specify placement, via a prefix: [assembly: AssemblyVersion("1.0.0")]
class C {
[return: MarshalAs(UnmanagedType.LPWStr)]
string Method() { ... }
} We could do the same thing with decorators: @decorator1
@decorator2
@export: decorator3 // decorates the export binding
export class C {
@route("/foo")
@method: permit("administrators") // decorates the method (optional here)
@return: serializeAs("json") // decorates the return value
method() { ... }
} I'm not necessarily saying we have to figure out what those mean, but that there's room in the design space to put together something meaningful that isn't dependent on whether the decorator is before or after the |
|
Given that In other words, you don't need to "keep track" of |
What about if a future proposal adds other keywords before class, for example When TypeScript adds support for ES decorators it will be an all-or-nothing switch, so we can definitely support |
That could be : export @mydecorator abstract class {} But I think export @mydecorator @abstract class {} |
Why would these keywords make sense before |
I'm not talking about those keywords before export
@dec
abstract class C {}
// vs
@dec
export abstract class C {} In discussing this with others on the TypeScript team, so far the consensus is that we haven't yet heard any negative feedback about our current placement of "before Also, there are some things that can't currently be done in a decorator (i.e. |
I guess i don’t understand the inconsistency. If you add or delete the export keyword, the declaration should be the same, conceptually - id find it very inconsistent if the ordering wasn’t “export”, “decorators for the thing”, “thing I’m exporting”. |
Coming from C#, I find the separation of the [attr1]
[attr2]
public abstract class C {
} Where class B {
}
@dec1
@dec2
class C {
} If I want to export class C {
@dec1
@dec2
method() {}
} I would refactor |
I'd be happy if the spec allowed me to chose one or the other as a personal preference, something like (simplified for brevity's sake):
Something like that would allow you to choose either |
@rbuckton The question is : What do you want to decorate with // Decorate "export" ?
@decorator export abstract class A {}
// Decorate the abstract class ?
export @decorator abstract class A {}
// Decorate a method ?
export abstract class A {
@decorator
method() {}
} |
OK, if TS has decorators before export, we are talking about a fairly large compatibility risk (where this proposal attempts to not require much changes in the decorator usage side, even if we are fine with requiring the definitions to change). For mitigations, we could
Does anyone know what syntax Babel uses here? |
As I understand Babel have to follow TS because it's the goal of Babel 7 to compile TS to JS. |
As I send earlier, using ES decorators will be a binary choice. If we end up choosing |
@mhegazy made a good point offline that not once in the almost-3 years since we shipped our decorators can we recall a complaint about the syntactic order of decorators with respect to the
As I understand it, decorator implementations can provide functionality that works under the older-style semantics as well as the newer-style semantics, so in those cases, you really are just providing a syntactic break with no provided semantic value. |
It’s worth noting that the lack of a complaint with “before export” isn’t the same as the presence of one with “after export”, especially in the context of build-time verifications like TS affords. Ultimately, I would prefer we only choose one, and not permit both, whatever the choice (I’ve expressed my preference above :-) ) |
Before I raised this issue, I searched related proposal repos to find usage of export with decorators, here are the results: @connect(...)
export class B {
render() {
// ...
}
} @customElement('nav-bar')
export class NavBar {}
@useShadowDOM
@customElement('my-expander')
export class Expander {
@bindable isExpanded = false;
@bindable header;
} NOTE: This code example is coming from the old decorator draft: https://tc39.github.io/proposal-decorators-previous/#example-aurelia-customElement
@define('my-element')
export class MyElement extends HTMLElement {} All three use |
I checked babel's implementation. It seems allowing only decorators-before-export. $ cat .babelrc
{
"plugins": [
"transform-decorators-legacy"
]
}
$ cat test0.js
@deco export class A {
}
$ npx babel test0.js
var _class;
export let A = deco(_class = class A {}) || _class;
$ cat test1.js
export @deco class A {
}
$ npx babel test1.js
test1.js: Unexpected token, expected { (1:7) |
Babel's implementation of the current decorator spec has not landed yet. The existing plugin is called Babel 7.x's parser has a
for
for example. The 7.x transform does not yet expose this parser plugin for use, but we intend to as we transition to aligning with the current proposal. |
Decos should be before export because that's how it reads better. We read the code more than we write it. This should be changed before it's too late. |
I don’t agree; to me, before reads like it applies to the exporting; after reads like it applies to the thing being exported. |
Correct me if I'm wrong but this is where it's going right now and I don't like it at all
BTW: I think somebody will eventually write babel-plugin which will swap the order for you which means people will stick with |
I originally thought it was weird to move the decorators between the export and class, but I’ve become used to it tho. I agree with both sides however. |
The pattern I've come to is export default
@decorator0
@decorator1()
@decorator2({meh: true})
class Foo extends Bar {
...
} |
This is a really unproductive thing to say. |
While it's may unproductive, I see it as helpless words. To be fair, in the history of JavaScript and Web platform standardization, most decisions are made by a few people, or vendors. When they did some unproductive things (I don't want to give examples here), the programmers were almost helpless. So I hope we could use our sympathy, and be tolerant to the unproductive thing from the programmers. |
Of course we have a way to disallow it - decorators explicitly supports that use case because TC39 believes it is NOT a bad practice. If we thought it was a bad practice it wouldn't be possible. |
I don't understand how JS spec can disallow a decorator change a class into a function? |
A class is already a function - decorators explicitly allow the capability to replace the value being decorated (when it's a class decorator). It would be trivial to ban that, but that was a critical use case for the decorator proposal - a good and necessary practice, not a bad one. |
This is quite a long thread so I don't know if it's been discussed already, but I took a look at existing precedent which may help inform which way the JS community leans. My search seems to indicate that many codebases favour Since #69 (comment) Babel has a boolean to toggle this. A simple search of Searching for GitHub's own codebase has uses |
Of coz there are many good cases. My point is not there is no good cases, but there are bad cases (for example replace a class to arrow function or async generators or any function which have a very different shape of the original class), and we have no way to disallow them in language layer, it must be developers to take the responsibility. So what I am arguing is, even decorators have the power to replace the class to very different things (and in such cases, the decorators may be more important than TYPE), it's not the common cases. My analysis of readability is based on the common cases that the importance of the information for reading code normally is: binding name = export > TYPE > decorators > implementation details. This is why |
I add again a vision already expressed (most of them have already been exposed). If I am not mistaken, the order of execution of the decorators is from right to left, bottom to top, i.e. the decorator closest to the class is the first to receive and decorate it, then the next, and so on. @decorator4th
@decorator3th
@decorator2th
@decorator1th
class A {
} Finally, the result of all this decorating process is exported. export
@decorator4th
@decorator3th
@decorator2th
@decorator1th
class A {
} In my humble opinion, this is common sense execution of the different parts. If you write export before decorators, it denotes that the class is exported before and decorators are executed after the export action, as a result, you can say that the exported class is not decorated. @decorator4th
@decorator3th
@decorator2th
@decorator1th
export
class A {
} Of course, we can understand lexical problems, performance problems, but please consider the psychological behavior. |
export class C {}
class C {}
export { C };
export { C };
class C {} The only imperative behavior associated with |
My question was dismissed by suggesting any usage of it was "suboptimal/unusual". This is clearly not the case as I explained a usage and pointed to a real world example.
This is the crux of the matter. Why conceptually overload the So, to return to my original question: From your experience, how do the devs who feel this is breaking up the class definition into multiple lines react to I'd imagine this looks really weird, considering the majority of what they've seen is based on the early implementations by babel and typescript that took a swing at things before it was fully thought out. Make no mistake, this was important work, but it seems weird to keep the unnecessary complexity after it's been identified just because of momentum. JS already has plenty of baggage to carry that can't be changed, so it seems unwise to introduce more when there's a chance to keep things simple and consistent from the get. From here, I'll switch back to being a lurker. I'm quite disappointed to have seen this issue resurrected from the dead, but I'm also excited about decorators finally being unstuck and being able to progress after years of being hung up on this. |
People are often connecting "export" with "public" and "decorators" with "annotations/attributes" which would make ES inconsistent with majority of other languages having these features. While technically they are different they mostly serve the same purpose from the users pov so you can't really blame people for assuming they are the same. I'd like to also point out we're talking about making what's essentially syntactic sugar potentially require more lines (in non-trivial cases where everything doesn't fit on one line), which is quite unfortunate. When we turn @Injectable({
providedIn: 'root'
})
export class A {
} into export
@Injectable({
providedIn: 'root'
})
class A {
} Might as well go with: @Injectable({
providedIn: 'root'
})
class A {
}
export { A }; |
This has felt like my entire interaction in this thread. The way I (and many others) experience the
It feels like you're completely ignoring what I'm trying to communicate to you here. Look, I have zero issues with this mental model, and it works quite well for me. It also, in my experience, works quite well for a lot of people. We do not find |
Oops, that's right, I forgot it. Now I understand the main mistake in this topic. Thanks @rbuckton and @pzuraq for your patient and very clear contributions. |
As the plenary, the consensus is allowing decorators before Thank you everyone to contribute to this 5 years long discussion. |
As can be observed by reading this thread, this issue can be considered
from several perspectives but all of them yield the same conclusion. There
is a popular demand for the idea that export should be always used before
any class decorator mark. Let me summarize what different perspectives can
be considered:
A. Syntax Perspective. From the syntax point of view, the correct
grammatical design considers any class decorator as a variant of expression
and any export sentence as a more abstract syntactic construction dealing
with the expression on the right side no matter if it is a decorator or
not. To my knowledge, language designers at typescript have put effort to
allow both alternatives but this is severe trouble at a grammatical level
causing annoying conflicts to be resolved and it is a bad design strategy
at least from an academic narrative. In particular:
Option 1. [Good] This option is for language engineers a happy design
because all occurrences of export sentences found only one reduction path
within the grammar converting any export particle followed by an expression
into a more abstract export sentence.
exp ::= exp PLUS exp |
exp MINUL exp |
exp TIMES exp | ...
@ IDENTIFIER class-declaration
export ::= EXPORT exp
Option 2. [Bad] In language design many grammar solutions are possible. I
am not sure what is the less bad solution. But just for the sake of
illustration, the following approach shows how this demand has less nice
grammar flavor.
exp ::= exp PLUS exp |
exp MINUL exp |
exp TIMES exp | ...
@ IDENTIFIER class-declaration |
export
export ::= @ IDENTIFIER EXPORT class-declaration |
EXPORT class-declaration
B. Semantic Perspective. From a semantic point of view, class decorators
can be explained as declarative marks to transform classes in any variant
of themselves. In the RUP narrative, we can say that UML Stereotypes can be
nowadays materialized in code by means of using decorators. Whilst class CX
{} is just a class, @task class CX {} is a variant of the concept of class
meaning a Task within a given Bounded Context or domain. When we relate the
class decorator with the export sentence to impose the proper order is very
relevant:
Option 1. [Good] This option can be read, from right to left, as follows: a
given class CX is firstly converted in a Task by applying the @task
transformation, and then, the result of that transformation is exposed as
an exported value to be received by an importer module. Here @task is
clearly a man-in-the-middle particle.
export @task class CX {}
Option 2. [Bad] The other option can be properly explained in JS
documentation but is clearly less intuitive. A foreign developer by
applying the same right-to-left read strategy would interpret as follows:
the class is exported to be received by an imported module and then a
@task transformation would be applied causing, apparently no effect,
because the export has been already processed according to the right to
left intuition. It is not necessary to mention that this reading rule is
the natural one taking into account how parsers work ;)
@task export class CX {}
C. Pragmatic Perspective. From a pragmatic point of view to fix both import
& export as prefix particles at the beginning of any line has also some
benefits. I must recognize that this argument is the weaker one in this
enumeration, so I beg not to take it into account. But that being said, in
my personal experience I have found graceful benefits assuming this order
when I have needed to create tooling processing any JS module. Instead of
using heavy (although powerful) solutions such as Babel or Babylon, I could
solve the past simple processing needs by reading each JS module as a text
stream. In those cases, the code was deployed within a service worker, so a
simple & straightforward solution was mandatory.
D. Behavioral Perspective. From the retrospective point of view, it was
said at the beginning of this spec that a class decorator is defined by
declaring a function. This idea is stressed by illustrating that in any
code place where a class decorator can be found a proper substitution is
allowed to use functional syntax. That is, the name of the decorator can be
used as a function invocation receiving the class as a parameter. This
means that the @ mark is just a graceful syntax sugar moving the language
to a more declarative flavor. And for me, it is a great hit because TC39
can be proud by stating that "JavaScript is constructed over JavaScript".
In some (corner) cases this functional/declarative duality has to resolve
relevant problems, especially in testing scenarios. Let's consider the two
options when the export particle is involved:
Opinion 1. [Good]. As explained before the following two lines can be
considered different dialectical ways of expressing the same behavior in
JavaScript. The first one uses a declarative, decorator-based, approach
whilst the second one is based on function invocation.
export @A @b @C class X {}
A(B(C(class X {})))
Opinion 2. [Bad]. In this other approach, the export participle has
interpretative problems from retrospective attention. The equivalent lines
stated above would be written as follows which cause clearly backward
compatibility issues. Maybe all is resoluble by this is a trouble to deal
with:
@A @b @C export class X {}
A(B(C(export class X {}))) // Error!
E. Cultural Perspective. That being said IMHO there is a solid
misleading revolving around the focus of the thread. What people are
demanding is not academic or scientific arguments in favor or against
options 1 o 2, as stated in the aforementioned perspectives. People just
demand a graceful Developer Experience. I can understand that it is the
more subjective one, but the language must be created and evolved attending
foremost to developer preferences. This goes to the benefit of all because
in another case a bad experience will be resolved at framework level, that
from our perspective in this group could be interpreted as an effort
failure.
El jue, 2 feb 2023 a las 20:58, HE Shi-Jun ***@***.***>)
escribió:
… Closed #69 <#69> as
completed.
—
Reply to this email directly, view it on GitHub
<#69 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABRFX6J5U2KWUWEH3BKNZBLWVQGVXANCNFSM4ETM7XMA>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Wow. Surprise twist in the eleventh hour and it’s like ... beautiful. A rainbow bikeshed! I raise my @ to all who fought on this storied battlefield, whether on the right side or the wrong side. Especially those who gave their lives ... you will not be forgotten. Three cheers for decorators! |
@hax so if I read this correctly, decorators can now go before export for classes, but the spec in
|
There were only two options for which the community was divided; either the export keyword comes before of after the decorator. In the end the allowing both options was the best solution TC39 could have made. It just took too long for this proposal to conclude... way too long. |
It feels like there should be a post-mortem focused on how to avoid 5 years of opinionated discussion over a single proposal |
@pwhissell most proposals are discussed for more years than that, so i'm not sure a postmortem is needed. It's just an expected part of the process for something that by and large we can never reverse (shipping something on the web, i mean) |
In this case, I think it would have been better to have removed exports entirely from the spec until this was sorted out. Decorators as a whole don't directly depend on this. Anyone using decorators could have just exported them on a separate line in the interim. Perhaps future contentious, nonintegral items could be evaluated for piece-wise deferment while the bulk of a proposal goes forward? |
@david0178418 this was not the blocker for the majority of the time that decorators were delayed. This came up again recently, but the main issues that delayed this feature were around performance and API complexity. We went through many, many iterations of design before we found one that worked and solved all constraints. I'm planning on writing a blog post on this when I have some time to outline all the different iterations and changes that were made for this feature over time. |
@pzuraq Ah, I see. Good to know. Thanks for the clarification. |
Ah makes sens. I'm curious to understand more about the performance issue |
Based on consensus of tc39#69
Comment moved to new issue: #503 |
@trusktr this issue has been settled, please don't continue commenting on it. |
or
or allow both?
The text was updated successfully, but these errors were encountered: