tc39 / proposal-decorators

Decorators for ES6 classes
https://arai-a.github.io/ecma262-compare/?pr=2417
2.74k stars 106 forks source link

@init: Why should it be removed from the proposal? if any decorator can always optionally return an initializer, @init: would not be necessary. #380

Closed pabloalmunia closed 2 years ago

pabloalmunia commented 3 years ago

First of all, I apologize for my English. It is pretty poor, and I may not be able to express my ideas very clearly.

After several days of building the experimental transpiler, I have a clearer picture of some fundamental aspects of the current decorator proposal. The most important and most impactful conviction is that it is a bad idea to include @init:.

the initialization is important

Initialization is an essential feature in the decorators environment. Several use cases can only be solved using some initialization once the class has been entirely built and decorated.

Whether to use an initialization function is up to the decorator developer, which may or may not utilize this functionality in its internal implementation.

@init: is an overload for the decorator user

The decorator mode invoked as @init: puts in the hands of the decorator user the need to include this clause if the decorator has been implemented so that it needs an initializer function. The decorator constructor must properly document this need, and the decorator user must always be aware of it. A decorator that requires an initializer will typically not be able to be used with a simple @ and should probably throw an exception if it is invoked in this way.

The need to express that a decorator needs an initializer should rest solely with the decorator developer and not need the decorator user support.

@init: is unnecessary for initialization, all decorator can return a initializer

Decorators invoked with an @init: may or may not include an initialize attribute on the object they return. Until the decorator is executed, we do not know if we will have to implement an initializer.

Identifying a decorator that requires an initializer is simple. We have to check if its return is an object instead of a function (which decorators do not need an initializer return). Even if it returns a thing, we have to check if this object has an initialize property.

This a simple helper function for process:

function __applyDecorator(result, origin, intializators) {
  if (typeof result === "undefined") {
    return origin;
  }
  if (typeof result === "function") {
    return result;
  }
  if (typeof result === "object") {
    if (typeof result.initialize === "function") {
      intializators.push(result.initialize);
    }
    return result.method || result.get || result.set || result.definition || origin;
  }
  throw new TypeError("invalid decorator return");
}

Although it may seem that the decorator execution is a very late moment to know if there is going to be an initializer or not, the truth is that it is the only moment where we know that an initializer has been returned.

@ as the only type to invoke decorator and desugars.

In our informal exercise in creating a transpiler, we have seen that we can use a simple support function so that all decorators can return:

For example, this decorator return an object with a method and an initilize:

function logged(value, { kind, name }) {
  if (kind === "init-method") {
    return {
      method(...args) {
        console.log(`starting ${name} with arguments ${args.join(", ")}`);
        const ret = value.call(this, ...args);
        console.log(`ending ${name}`);
        return ret;
      },

      initialize(initialValue) {
        console.log(`initializing ${name}`);
        return initialValue;
      }
    };
  }
}

class C {
  @logged
  m() {}
}

let c = new C();
c.m(1);

This code "desugars" to the following:

const _member_initializers = [];

class C {
  constructor() {
    _member_initializers.forEach(initialize => initialize.call(this));
  }
  m() {}
}

C.prototype.m = __applyDecorator(logged(C.prototype.m, {
  kind: "init-method",
  name: "m",
  isStatic: false,
  isPrivate: false,
  defineMetadata: __DefineMetadata(C.prototype, "m")
}), C.prototype.m, _member_initializers);

let c = new C();

c.m(1);

Advantages and disadvantages of eliminating @init:

Pro

Cons

In my opinion, the advantages outweigh the disadvantages.

Keep the constraints

Remove @init: keeps the constraints set by these three rules:

A: The "shape" of the class should be apparent syntactically, without executing code.

B: It should not be too complicated to process decorators, as this corresponds to a complex implementation.

C: Minimize or eliminate observable mutations to objects while setting up the class.

ljharb commented 3 years ago

Without it, how can i have a @bound decorator on a method that does this.foo = this.foo.bind(this) on the instance at construction time?

pabloalmunia commented 3 years ago

Off course, @ljharb, you need an initialize, but in my proposal all decorators can return a initialize function for this kind of operation. I propose to remove the @init keyword, not the ability of decorators to return an initializer.

ljharb commented 3 years ago

Wouldn’t that penalize the performance of every decorator that doesnt need an initializer?

senocular commented 3 years ago

Wouldn’t that penalize the performance of every decorator that doesnt need an initializer?

Is it enough that it matters? Are there no other ways to optimize for this without the user having to explicitly specify it? Why not default to allowing initialization and allow users to opt in to optimization with something like an @restricted: instead? Are there other places in the language today that has a similar restriction like this for the (sole?) purpose of engine optimization?

ljharb commented 3 years ago

Performance impacts for those who don’t need the feature was the primary reason decorators got rejected for stage advancement last time - so yes, it matters.

blikblum commented 3 years ago

Wouldn’t that penalize the performance of every decorator that doesnt need an initializer?

Is it enough that it matters?

Heavy decorator (in current state) user here.

I'm in the same boat: the benefits of ditching @init outweighs its drawback (the performance impact) .

Seems clear now, given the efforts to finish this proposal, that is not possible to conciliate optimal developer experience with a zero impact performance / optimal engine optimization.

The @init syntax is specially awful since affects decorator users experience not only creators, as explained in OP

All in all, even with the performance overhead of decorator implementations being used today (a lot worse than this new proposal), its widely used without significant complaints about performance.

pabloalmunia commented 3 years ago

@ljharb the previous version allowed a complete class mutation by decorators, that it breaks all optimization strategies, since it requires repositioning the class again and again, as it changes unexpectedly. The initialization is a functionality that does not modify the class structure, although it does include new steps for the application of the decorator. The performance loss of this feature is very very small and the optimization is posible because is a known, stable and non-mutable functionality.

I have considered everything indicated in the working group document "Decorators Design Space Analysis" before making the proposal and, in my opinion, keep the performance constrains.

ljharb commented 3 years ago

@pabloalmunia “has an own property or not” is part of the class’s structure.

pabloalmunia commented 3 years ago

@pabloalmunia “has an own property or not” is part of the class’s structure.

Sorry, I don't undestand very well your message.

Yes, the initialize function can add a own property to the this when is invoked after the constructor. The object's structure is defined by the class's structure (the prototype), the constructor execution (usually with this.newProperty = X) and the initialize function executed after the constructor.

Maybe I am completely wrong and this proposal is a mistake. Please feel free to make all kinds of criticisms of this proposal.

ljharb commented 3 years ago

Maybe it’s my confusion then; are you suggesting that any decorator can always optionally return an initializer, and that @init: would not be required to do so?

pabloalmunia commented 3 years ago

Maybe it’s my confusion then; are you suggesting that any decorator can always optionally return an initializer, and that @init: would not be required to do so?

Yes, this is the proposal.

pzuraq commented 3 years ago

I do think this proposal makes sense and would result in much better developer ergonomics overall, but as @ljharb mentioned above, the reason decorators were rejected last time was due to performance overhead that was not related to the use case of the decorator. Adding the ability for every decorator to optionally return an initializer would introduce some overhead, even in the cases when the decorator does not return an initializer.

That said, the previous iteration of decorators was far more dynamic, and this would be less so. I think we should continue writing the spec with the proposal as is, including @init:. However, I do think this could be brought up as a potential alternative.

rbuckton commented 3 years ago

This is similar to the finisher functionality in an earlier draft of the proposal. This was rejected because implementers felt they needed a syntactic opt-in to avoid paying the cost of having to check for, and run, instance finishers.

I'm not 100% certain I agree with that concern. It seems reasonable enough to me to have a class constructor perform a check during construction as to whether there were any instance finishers registered and evaluate them. Yes, a finisher could result in a shape change, but so could any other method call in the constructor.

If we did want to pursue dropping @init: for finishers, we could possibly leverage the context object to add a finisher, similar to what we're doing for defineMetadata:

type ClassMethodDecorator = (
  value: Function,
  context: {
    kind: "method";
    name: string | symbol;
    access?: { get(): unknown; };
    isStatic: boolean;
    isPrivate: boolean;
    defineMetadata(key: symbol, value: unknown): void;
    addFinisher(finisher: Function): void;
  }
) => Function | void;

This would simplify the return-type of a decorator, since you wouldn't have to pick between returning Function, { definition, finisher }, or { get, set, finisher }.

This would also help with the confusion between the different ways initialize is used in the proposal that I mention here: https://github.com/tc39/proposal-decorators/issues/384#issuecomment-814529844

rbuckton commented 3 years ago

If we still want a syntactic opt-in with @init:, we could just have addFinisher be undefined, or have it throw if not defined with @init:.

pabloalmunia commented 3 years ago

addFinisher into the context is a very good idea. We have put too many options in the decorator's return.

If we need a syntatic opt-in, perhaps we can define something syntax over the decorator function, not over decoration use. For example:

function init:decorator(value, context) { /*...*/ }

This init: before the decorator function name defines a decorator with initialization. In this way, the user of the decorator does not need to know the decorator's internal behavior.

ljharb commented 3 years ago

That would mean that init decorators were a special kind of function object, but decorator functions are supposed to be able to be any function.

pabloalmunia commented 3 years ago

That would mean that init decorators were a special kind of function object, but decorator functions are supposed to be able to be any function.

The init: before a function is a "pragma" or "directive" that indicates this funcion is used as init decorator, but the functions is a regular function. If we already had function decorators it could be something like:

@init:
function decorator() {}

But, perhaps this approach is a bad idea. The important thing is to ensure that the user of the decorator does not know whether the decorator in its implementation uses an initialization function or not.

ljharb commented 3 years ago

The user of the decorator already has to know if the decorator expects to be used in the location the user is invoking it. I think it's perfectly fine for the user of the decorator to need to know if the implementation requires @init: or not (altho obviously it's better if they don't need to know).

pzuraq commented 3 years ago

Another issue with the placing the syntactic opt-in on the definition of the decorator rather than the usage of the decorator is that, in order to understand the shape changes, it requires the engine to parse and process the decorator before it parses the class. This is not impossible to overcome, but it was something that they were concerned about with the static decorators proposal.

That said, if the decorators were still "just functions", with a little bit of extra static information attached to them, then it wouldn't have a lot of the same issues that static decorators did. For instance, it would be polyfillable, and wouldn't require build-tools to create a cross-build-tool standard for defining and applying them.

I think the issue is that for it to be fully polyfillable, that static info needs to be something that can exist on the function today, like:

function initDecorator() {}

initDecorator.isInitDecorator = true;

And then we're back in a world where engines cannot statically analyze what a decorator can do, which is a non-starter 😕

ljharb commented 3 years ago

Additionally, making syntax required for the decorator makes the proposal not partially transpileable - iow, a package couldn't publish a transpiled decorator that was usable by both a transpiled user and a native user.

senocular commented 3 years ago

This would simplify the return-type of a decorator, since you wouldn't have to pick between returning Function, { definition, finisher }, or { get, set, finisher }.

I really like the idea behind this.

It would also be nice if we didn't have to double the variants of kind with the introduction of @init, maybe by having the presence of addFinisher being dependent on whether or not @init was used?

type ClassMethodDecorator = (
  value: Function,
  context: {
    kind: "method"; // <- never "init-method"
    name: string | symbol;
    access?: { get(): unknown; };
    isStatic: boolean;
    isPrivate: boolean;
    defineMetadata(key: symbol, value: unknown): void;
    addFinisher?(finisher: Function): void; // <- present if a @init (or should it be @fini?) decorator
  }
) => Function | void;

And if the value type always matched the return type, that would make it much easier to manually compose decorators. For example as an author if you wanted to create a new decorator from two other decorators, that should effectively be as simple as:

function decoAB (value, context) {
  return decoB(decoA(value, context), context)
}
pzuraq commented 3 years ago

I like that direction actually, it's similar to the decision not to make static-method or private-method a type of kind due to combinatorial explosion. Finishers are only allowed if you have @init:, and the return types of decorators generally become a lot simpler overall.

I'll make a PR with those updates.

pabloalmunia commented 3 years ago

I'll make a PR with those updates.

This PR include a context.addInitializer() for @init: decorators: https://github.com/tc39/proposal-decorators/pull/388

trusktr commented 3 years ago

Why do decorators have to return anything?

What if we eliminate decorator return values, and make context into a robust API?

// Composition:
function myDeco(context) {
  if (context instanceof MethodDecoratorContext) {
    decoA(context)
    decoB(context)
  } else if (context instanceof PropertyDecoratorContext) {
    decoC(context)
    decoD(context)
  } else {
    throw new Error('This decorator can only be used on methods or properties.')
  }
}

As for removing @init, I think if the DecoratorContext classes have methods for defining initialize logic (or more generally defining any features), then this can all be push based: method calls tell the engine when to use initialize (or more generally when to use any feature), and the engine therefore does not need dynamic checking via iteration or similar.

So if a dev does not call addFinisher or addInitializer (or addWhatever) then the engine does not expect those features are used; it is all optimized by default.

At least, that's how I view it. Did I overlook something?

Note, classes like the abstract DecoratorContext and its subclasses are provided by the host, built-in, but also polyfillable.

ljharb commented 3 years ago

Are you suggesting an api that relies on instanceof and mutation? There’s not many more ways it could get worse to me beyond those.

trusktr commented 3 years ago

That's what JavaScript is about to begin with...

How about freezing the prototypes and the constructors, making them immutable, for the sake of this particular API so engines will have guarantees they need in this case?

nicolo-ribaudo commented 3 years ago

That still doesn't guarantee that it will work: you could have a decorator applied to a class in a different realm, with different built-in constructors (which is the reason why we have Array.isArray rather than just instanceof Array).

taralx commented 3 years ago

Crazy idea: Annotate decorators with required capabilities.

@decorator({capabilities: ['init']})
function deco(value, context) {
  context.addInitializer(...)
}
pzuraq commented 3 years ago

Said capabilities would need to be fully statically analyzable, meaning that you couldn't do something like:

let capabilities = ['init'];

@decorator({ capabilities })
function deco(value, context) {
  context.addInitializer(...)
}

Any amount of dynamism leads us back to the same problems that led to the @init: syntax in the first place.

taralx commented 3 years ago

Does anyone have measurements of this performance impact or is this a speculative concern?

taralx commented 3 years ago

Any amount of dynamism leads us back to the same problems that led to the @init: syntax in the first place.

This is letting the perfect be the enemy of the good. If someone uses dynamic capabilities and you can't assess them (e.g. it's not just a local constant), then assume worst case. That's assuming you can't just work speculatively a la opt/deopt.

ljharb commented 3 years ago

@taralx you may be missing that browsers have already blocked the proposal if there is any dynamism. The choice is between "nothing" or "static", regardless of what aphorisms you may want to apply.

pzuraq commented 3 years ago

@taralx IMO the @init: syntax is a good compromise that allows us to ship a proposal which has a balance between acceptable DX and reasonable performance. We could also let perfect DX be the enemy of a good proposal that would enable much better DX than is currently possible without decorators or experimental transforms.

We will be discussing alternatives as we bring this proposal to committee, but the previous proposals all failed because of their inability to work with performance constraints. I can also say for sure that these constraints are not entirely theoretical - we tested the stage 2 transforms with Babel on our flagship application and found them to be a significant regression over the legacy transforms. More dynamism means higher costs, and it's not always possible to optimize on the fly, especially with class definitions and decoration.

blikblum commented 3 years ago

I can also say for sure that these constraints are not entirely theoretical - we tested the stage 2 transforms with Babel on our flagship application and found them to be a significant regression over the legacy transforms.

Its not fair to compare the implications of "@ init:" syntax removal with stage 2 proposal which allowed all sort of dynamism like changing the class in a field decorator. Even without "@init:" syntax, the current proposal is more strict and allows more engine optimizations than the legacy transforms which you used as a base to compare.

@taralx you may be missing that browsers have already blocked the proposal if there is any dynamism.

I think is the case to discuss with browser devs the implications of this specific change.

AFAIK the removal of "@ init" would need to keep a registry of initializers for each decorated class and run them in constructor. How running the initializers would impact performance ? Native implementation, for sure, can use different data structure other than an array which is used in transpilation

Other concern is the possibility to change the instance shape like a decorator setting a new property to the instance.

But how is this different from setting a property in constructor directly or indirectly ?

import {mayMutate} from './helpers.js'

class MayMetamorphic {
  constructor (type) {
     this.type = type
     if (type === 'foo') {
        this.x = 1
     } 
     this.configure()
     mayMutate(this)
  }

  configure() {
     if (this.type === 'bar') {
        this.y = 2
     }     
  }
}

The compiler cannot determine the instance shape without running the constructor.

Asking and discussing the performance overhead of "@ init" removal with browsers, to have an actual measure and knowing if is doable implementing, would not hurt at all.

Concurrently, discuss with main decorator consumers (angular / ember) if this performance overhead is acceptable or not, considering the improvement in DX.

ljharb commented 3 years ago

@blikblum those arguments have been tried, and failed. Browsers will only allow this proposal to advance if the shape of a class and its instances, modulo code in the constructor body or an initializer that mutates, can be statically known. The DX impact/improvement has no impact on the state of things here. There's simply no room for any choice but "no decorators, ever" or "no dynamism, in any form".

senocular commented 3 years ago

There's simply no room for any choice but "no decorators, ever" or "no dynamism, in any form".

"no decorators, ever" doesn't seem like an unreasonable option here. The fact is, we're already using legacy decorators heavily today without official language support. And we can continue to do so thanks to tools like babel and typescript. Types, enums, JSX - these are all features used in JavaScript (or some form of it) that are not part of the language itself. Decorators can simply be another item in that list. It would mean 0 disruption to the ecosystem with ergonomics people are familiar, and as far as I can tell, seem happy with.

taralx commented 3 years ago

I am concerned that "browsers" are being invoked in abstract. Those are people, most of whom have GitHub accounts. Let's get them in here to comment on this?

pabloalmunia commented 3 years ago

Well, the meeting of TC39 is the official place for this kind of discussion. It's a very open committee. I'm not a member, but I follow the meetings with these notes https://github.com/tc39/notes and the agendas for next meetings in https://github.com/tc39/agendas

If you search "decorator" in the repos, you can see references and discussions from March'15.

In January'19, Daniel Erhemberg presented a proposal about decorators (currently implemented in Babel). The discussion was very important, as the highly dynamic mechanisms of mutation of class elements using decorators were considered quite damaging for the optimization strategies of javascript engines.

This is the Daniel presentation: https://docs.google.com/presentation/d/1Frdz3xTJBVGLlZeJEuM0hV_EyV97IJg-ny0DPgtuwHM/edit#slide=id.p

This is the note about that discussion: https://github.com/tc39/notes/blob/8711614630f631cb51dfb803caa087bedfc051a3/meetings/2019-01/jan-30.md#decorators-for-stage-3

The current version is a difficult balance between flexibility, mutuality, and staticity. I am the first one to propose to remove @init:, but everybody needs to understand how complicate is to consider all points of view.

blikblum commented 3 years ago

The DX impact/improvement has no impact on the state of things here.

This is sad and somewhat contradictory considering that the whole point of decorators is to improve DX.

Given how widespread decorator usage is, even with all performance it incurs due to design and transpilation, i believe most devs would not mind this minor performance overhead in name of better DX, even because would still be ways faster than current.

ljharb commented 3 years ago

@blikblum i agree with you. however, browsers are the ones who decide to implement something or not, and they won't implement anything that has the referenced performance overhead. A standard only works when everyone agrees to follow it; the standard simply can not compel implementations to do anything.

taralx commented 3 years ago

Thanks for the links to the notes.

they won't implement anything that has the referenced performance overhead

Which referenced overhead, and is that noted somewhere else? I did not get that from the meeting notes.

ljharb commented 3 years ago

The one we've been discussing in this thread, that was mentioned immediately prior to my comment.

blikblum commented 3 years ago

and they won't implement anything that has the referenced performance overhead.

They will, because needs to support @init:

And will fall in this code path frequently because @init: is pretty common in the wide.

The venerable bound decorator, used as example since legacy proposal, needs it. Decorators from mobx would require it to get rid of calling makeObservable in constructor. Lit also would require it at least for the customElement decorator (used in pretty much all examples). I don't know if Ember or Angular requires this pattern frequently.

Ironically, two of the examples in the proposal that uses @init does not change class or instance shape (they just registers the element and the event handler):

@init:customElement('my-element')
class Example {
  @init:eventHandler('click')
  onClick() {
    // ...
  }
}

BTW: even with @init is important to let the decorator proposal ship (for myself i already laid out ways to avoid or make optional @init: in my decorators). So i wont bother more.

ljharb commented 3 years ago

@blikblum yes, the bound decorator needs @init, because it's static, and that's what implementers have insisted on as a constraint.

blikblum commented 3 years ago

Which referenced overhead, and is that noted somewhere else? I did not get that from the meeting notes.

AFAIK there are two: One directly due to the need to keep a registry of initializers for each class and run them in constructor

Other indirectly, since the initializers could potentially change the shape of instance or prototype thus preventing advanced optimization like mapping a JS class to a compiled class structure

The first is incontestable, without @init any class with a decorator would require the initializers registry even if does not use initializers. With @init, only class with @init decorators would need this registry

The second is also real but is more a miss of opportunity to optimize. Its not guaranteed to achieve the optimization because, as i pointed early, there are many ways to change class or instance shape even without decorators or initializers, making hard or undoable to statically analyze.

Finally i could write decorators that change instance shape without initializers like the below implementation for bound:

const BOUNDED_METHODS = Symbol()

function bound(value, { name, setMetadata }) {
  setMetadata(BOUNDED_METHODS, value)
}

function base(value, { kind, name }) {
  if (kind === "class") {
    return class extends value {
      constructor(...args) {
        super(...args);
        const boundedMethods = C.prototype[Symbol.metadata][BOUNDED_METHODS] ;
        // walk in bounded methods and mutate instance at will        
      }
    }
  }

  // ...
}

@base
class C {
  message = "hello!";

  @bound
  m() {
    console.log(this.message);
  }
}
pzuraq commented 3 years ago

@blikblum it's a given that class decorators will be able to change the shape of the class, either by mutating the prototype or by returning an entirely new class. This is different from element decorators being able to change the shape arbitrarily alone.

@senocular

"no decorators, ever" doesn't seem like an unreasonable option here. The fact is, we're already using legacy decorators heavily today without official language support. And we can continue to do so thanks to tools like babel and typescript. Types, enums, JSX - these are all features used in JavaScript (or some form of it) that are not part of the language itself. Decorators can simply be another item in that list. It would mean 0 disruption to the ecosystem with ergonomics people are familiar, and as far as I can tell, seem happy with.

I disagree here on a few points:

  1. The current status quo is not ideal, and already falling apart. TS decorators do not work on class fields anymore. Numerous libraries have broken already due to changes in field semantics which broke the way that these decorators were supposed to work. Babel decorators do still work, but in a different way, resulting in fragmentation. In the long term, it's not impossible that further changes to the spec could cause both transforms to end up in a broken state. This is not future proof at all.

  2. The current transforms are acceptable in terms of performance, but far from ideal. They require a much larger payload size than untranspiled decorators would, and have a pretty decent runtime performance impact. Any of the improvements that engines have made to classes using the latest features are likely wiped out here.

  3. Decorators today cannot work at all with private fields or methods, and that's a pretty big gap. As these features see more adoption, that gap is only going to get more apparent and worse overall.

pabloalmunia commented 3 years ago

First of all, I understand the differences between native code and transpiled code, as well as the differences between simple code for testing and complex code for real use.

Anyway, I have made a small benchmark between a single classs without decorators, a class with empty decorators and a class with empty init:decorators. The latter two have been transpiled before the test and the transpilation time has not been taken into account.

// class without decorators
class TestClass {
  p = 0;
  run() {
  }
  get g() {}
  set g(v) {}
}
// empty decorator
function emptyDecorator() {}

@emptyDecorator
class TestClass {
  @emptyDecorator
  p = 0;
  @emptyDecorator
  run() {
  }
  @emptyDecorator
  get g() {}
  @emptyDecorator
  set g(v) {}
}

(see the transpiled version)

// empty init: decorator
function emptyDecorator() {}

@init:emptyDecorator
class TestClass {
  @init:emptyDecorator
  p = 0;
  @init:emptyDecorator
  run() {
  }
  @init:emptyDecorator
  get g() {}
  @init:emptyDecorator
  set g(v) {}
}

(see the transpiled version)

As you can see, the cost of decoration is important and the cost of decoration with @init: even more so. Of course, values may vary according to implementation, native execution and many other factors, but we cannot say that they are equally heavy.

blikblum commented 3 years ago

Anyway, I have made a small benchmark between a single classs without decorators, a class with empty decorators and a class with empty init:decorators

Comparing with babel implementation (legacy and stage 2) would be useful. Would @init: be faster or slower than current code being used in the wild?

As you can see, the cost of decoration is important and the cost of decoration with @init: even more so.

I believe no one here is saying / thinking that allowing initializer in any decorator is free of performance overhead.

Rather than questioning the actual impact in real world usage (most of the time is not relevant) i would say:

The decorators language feature is not the best option for hot code path or performance sensitive code (with @init or not).

As is today with delete keyword or with specific features in other languages.

BTW: i'm a performance minded programmer. Used to analyze compiler generated assembler to evaluate code patterns. Maintained a db access library where i used pointers at will. This does not prevented me to use Object Pascal reference counted interfaces, with its high performance overhead, in name of better DX. Use the right tool / language feature for the right job.

pabloalmunia commented 3 years ago

Comparing with babel implementation (legacy and stage 2) would be useful. Would @init: be faster or slower than current code being used in the wild?

These result are getting in other machine (sorry if it is confusing). The comparison is the important thing.

It is important to understand that the Experimental Transpiler is too simple an the Babel Transpiler is a Transpiler for the real world.

blikblum commented 3 years ago
  • Empty @init: decorator (Experimental transpiler): 302 millions of new object per second
  • Empty decorator (Babel legacy): 230 millions of new objects per second
  • Empty decorator (Babel stage 2): 205 millions new objects per second

Many thanks.

This confirms (implementation constraints apart) what we were hinting above. This proposal, even supporting initializer, is faster than babel implementation, and should be faster enough at least to the use cases in the wild