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

Parameter decorators #47

Open
littledan opened this issue Feb 10, 2018 · 19 comments
Open

Parameter decorators #47

littledan opened this issue Feb 10, 2018 · 19 comments

Comments

@littledan
Copy link
Member

See tc39/proposal-decorators-previous#45

Leaving for v2.

@bmeck
Copy link
Member

bmeck commented Feb 28, 2018

+1 to leave for follow on

@mgechev
Copy link

mgechev commented Sep 25, 2019

Angular is heavily using parameter decorators as part of the dependency injection capabilities of the framework. Is this feature still planned as a follow up proposal? What's the plan for the intermediate state when browser vendors will support class decorators but will not have parameter decorators support? Although this is probably a concern mostly for compilers, I believe it's a topic we should discuss.

@littledan
Copy link
Member Author

@mgechev I believe that parameter decorators will likely be able to desugar into method (or function) decorators. So tooling can do exactly that desugaring when we are in this intermediate state.

@glen-84
Copy link

glen-84 commented Apr 9, 2022

@bakkot,

Well, to confess my biases here, I am (at present) very firmly against parameter decorators

May I ask why? How would libraries like Angular and TypeGraphQL function without parameter decorators?

@bakkot
Copy link

bakkot commented Apr 9, 2022

"There exists a library which would use this feature" is not, in itself, sufficient reason to add a feature. For a syntactic feature to be worth adding there needs to be an affirmative case that it would make some very broad class of problems significantly easier or clearer. I don't think parameter decorators meet that bar. It has not been my experience that parameter decorators make code clearer at all, in fact; most code that uses them (yes, including Angular) seems to me like it would have been clearer if it had been written in a different way entirely. (I think the ecosystem bears this out.)

Perhaps I'm wrong, and on further discussion we'll determine that there are actually many cases where parameter decorators are necessary to clearly express intent. I look forward to that case being made. But right now, I have not seen many cases where I think code is or would be expressed more clearly with parameter decorators.

@glen-84
Copy link

glen-84 commented Apr 9, 2022

-There exists a library which would use this feature
+There exist multiple libraries which do use this feature

I started a list here, and there are already 8 projects using property decorators, some of them very popular. The document can be edited by anyone, so feel free to add more.

most code that uses them (yes, including Angular) seems to me like it would have been clearer if it had been written in a different way entirely

I'm sure they would be interested in your suggested alternatives. 🙂

@pzuraq
Copy link
Collaborator

pzuraq commented Apr 9, 2022

I think it would be good to discuss the use cases for parameter decorators, not just the existing libraries that use them. As @bakkot has noted, just the existence of usage in the ecosystem is not really enough to motivate a feature.

So far, there are two concrete use cases for parameter decorators:

  1. Dependency injection
  2. Parameter/argument validation

Definitely would like to see if there are more, these are somewhat motivating but DI is fairly divisive in terms of motivation and validation can be done in other ways (e.g. on methods/functions directly), though it would be less ergonomic.

@pabloalmunia
Copy link
Contributor

pabloalmunia commented Apr 10, 2022

In my opinion, there are several use cases for parameter decorators. Some of them are

  • Simple annotation: identify parameters without effect, but valuable for documentation tools.
  • Add metadata: adding information about parameters so that other decorators can act on the necessary metadata about the function input values.
  • Validation: checking values, types, ranges, and structures in the parameters.
  • Transformation: adaptation of the parameters' values, types, and structures.
  • Advanced default values: adding features to parameter defaults values.
  • Dependency injection: dynamic inclusion of dependencies using decorated values.

@joelday
Copy link

joelday commented Nov 4, 2022

@pzuraq

DI is fairly divisive in terms of motivation

Can you quantify that?

@pzuraq
Copy link
Collaborator

pzuraq commented Nov 4, 2022

@joelday when it was raised in committee, there were multiple members who expressed that DI was not a very strong motivating use case on its own, and that they would prefer there be more use cases to justify the feature. We haven't revisited the discussion since then as I've been focused on landing the main decorators proposal first. When that's complete, I intend to dig into the concerns behind parameter decorators more thoroughly.

@joelday
Copy link

joelday commented Nov 7, 2022

@pzuraq Thanks for the clarification. The way you worded it gave me the impression that personal bias for/against DI itself was a factor.

@joelday
Copy link

joelday commented Nov 7, 2022

@pzuraq I'd like to also submit that the entire Visual Studio Code codebase makes use of them for DI, and quite successfully so.

https://github.com/microsoft/vscode/blob/33040b9696aa918784c818c5d375ec4a4c1f8e85/src/vs/code/electron-main/auth.ts#L67

This isn't to suggest that a single codebase should influence the direction of standards, but that libraries and their usage alone aren't necessarily the full picture.

@pzuraq
Copy link
Collaborator

pzuraq commented Nov 7, 2022

@joelday yes, I am aware, as are most members on the committee. Usage of pre-stage-4 transforms or features is generally not seen as strong motivating example, the logic being that the feature should make sense on its own merits and should not generally be advanced just because it was adopted by a large organization, community, or codebase.

There is a strong history of pushing back against accepting existing APIs in my experience, some examples include Promises (the final API differs in key ways from community libraries that were popular at the time), classes and class fields ([[Set]] vs [[Define]] semantics were a huge discussion and major breaking change for many users), and of course decorators, but there are many others as well.

Personally I am a pragmatist and am usually more happy to accept the existing community developed APIs where possible, but when working in committee and trying to advance things I generally find I have to work with a large number of different viewpoints to come to a consensus, and sometimes that means going in a different direction.

@trusktr
Copy link
Contributor

trusktr commented Nov 12, 2022

DI can be achieved with current decorators using a different format:

@injectable
class Foo {
  @inject([A, B])
  someMethod(a, b) {}
}

@guyca
Copy link

guyca commented Nov 13, 2022

@trusktr Method injection support isn't good enough. The idea is to support both automatic constructor injection and manual injection in-through the constructor. This allows class instantiation to be decoupled from dependency resolving and also for classes to be unit tested easily.

@Injectable(AppGraph)
class ConstructorInjection {
  constructor(serviceA?: ServiceA, serviceB?: ServiceB);
  constructor(@Inject() private serviceA: ServiceA, @Inject() private serviceB: ServiceB) {}
}

// In your production code, instead of this:
new ConstructorInjection(new ServiceA(), new ServiceB());
// You can do this:
new ConstructorInjection();
// While still being able to easily unit test by passing mocks in a sane manner
const uut = new ConstructorInjection(mock<ServiceA>(), mock<ServiceB>());

@joelday
Copy link

joelday commented Nov 14, 2022

the logic being that the feature should make sense on its own merits

Absolutely agreed.

I noticed that the last meeting notes are from 2019. Do you have anything more recent?

@pzuraq
Copy link
Collaborator

pzuraq commented Nov 14, 2022

I would refer to the TC39 plenary notes. We have kept some notes internally, but the overhead of note taking is a decent amount and I'm stretched thin as is so I have stopped formatting/uploading them myself. The disagreements/issues I'm referring to come up in plenary however, we don't typically discuss them in the Decorators meeting.

@rbuckton
Copy link
Collaborator

I was just discussing parameter decorators with @littledan, and wanted to capture some of my notes here for the inevitable parameter decorators proposal. Parameter decorators are extremely valuable for a number of scenarios, including constructor parameter injection in a DI system like the one VS Code uses.

While that scenario also strongly depends on some capability for associating metadata (as is being discussed in https://github.com/tc39/proposal-decorator-metadata), I think the following approach to a native parameter decorator would solve the other side of the problem.

Much like a class field, a parameter has no reified declaration for replacement. As a result, its target would likely be undefined, just as with class fields.

In addition, a parameter has an initial value that is either provided by the user as an argument, via an initializer, or just undefined. Therefore, a parameter decorator could potentially support an initializer mutator function as a return value, just as with class fields:

function paramDec(target, context) {
 target; // always `undefined`
 return value => value; // can observe/replace argument passed to parameter
}

As far as the provided context, there are a number of useful properties we could provide such as the parameter name (or undefined when the parameter consists of a binding pattern), the ordinal position of the parameter, and whether the parameter is a rest argument. We could also potentially include an addInitializer() callback that could run before the function body is evaluated.

This might look something like the following:

type ParameterDecoratorContext = {
  kind: "parameter";
  name: string | undefined;
  index: number;
  rest: boolean;
  addInitializer(cb: (this: unknown, ...args: any[]) => void): void;
};

We could also add additional context information related to the method being decorated, i.e.:

type ParameterDecoratorContext = {
  ...
  function: 
    | ClassMethodDecoratorContext
    | ClassSetterDecoratorContext
    | ClassDecoratorContext
};

@cjnoname
Copy link

cjnoname commented Sep 1, 2024

Any news on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests