Decorators vs Annotations (was April 10 2014 Meeting Notes)

# Erik Arvidsson (11 years ago)

On Tue Apr 15 2014 at 10:27:23 AM, Rick Waldron <waldron.rick at gmail.com>

wrote:

Decorators for ES7

(Yehuda Katz)

Slides: (need slides)

YK: Presenting aspects of common use cases not yet covered by ES6 class.

Knockout.js example (compute the value of a property)

WH: Do you want to use functors to produce functions that are per-class (i.e. on the prototype) or per-instance?

AWB: Per instance wants to be handled in the constructor

YUI example (a readonly property)

LH/YK: Sometimes you want to say a method is readOnly

AWB: No declarative way to describe the per instance state

Angular example

LH: (explanation) when I declare a class, I also want to register it with some other system

ES6 Experiments: Angular

@NgDirective('[ng-bind]')
class NgBind {
  @Inject([Element])
  constructor(element) {
    this.element = element;
  }
}

AWB: The "@" used to define an annotation

JH: Point out that this is inert meta data

The main use case for Angular is for dependency injection. For that you only need meta data.

Generally decorators are more powerful than annotations since decorators can add the meta data as a side effect.

However, the main issue we ran into with decorators is annotating function parameter:

function doSomething(@inject xhr) {
  ...
}

With decorators it could course be rewritten as:

@annotateParam(inject, 'xhr')
function doSomething(@inject xhr) {
  ...
}

Maybe that is sufficient? Maybe that could be done as syntactic sugar?

# Ron Buckton (11 years ago)

One options is to use a default instead of a decorator:

function doSomething(xhr = inject('xhr')) {
}

Alternatively, a python-like decorator in an argument list position could function like this:

// as written:
function doSomething(@inject xhr) {
}

// approximate desugaring:
function doSomething(xhr) {
  xhr = inject(xhr);
}

Unfortunately, you lose the ability to use the decorator for pure metadata definition and can only use it for mutation during function invocation.

I experimented with decorators in a fork of TypeScript over a year ago. The first run ended up with a very ugly meta type system: gist.github.com/rbuckton/b2d259d036224a4477f4#metadata-based-param-decorators.

When I was investigating decorators I was looking for a model that worked in two ways:

  1. A way to express metadata about something during its definition (akin to C# attributes)
  2. A way to mutate/replace something during execution

If I wanted to be able to have both the metadata-at-definition and mutation-at-execution semantics for argument decorators, I'd need something like this:

// as written:
function doSometing(@inject xhr) {
}

// approximate desugaring:

// compiler generated identity function
//   using `_doSomething_xhr` here, but in practice this wouldn't be accessible as an identifier
var _doSomething_xhr = function(_) { return _; };

function doSomething(xhr) {
  // execute the (possibly mutated/replaced) generated identity function during execution of `doSomething`
  xhr = _doSomething_xhr(xhr);
}

// decorate the generated identity function
//   allows for metadata-at-definition semantics
//   `inject` can mutate/replace the identity function which can then affect the execution semantics later
_doSomething_xhr = inject(_doSomething_xhr); 

The same approach could would for fields in a class (if they are reintroduced in ES7):

// as written:
class Sample {
  @inject x;
}

// approximate desugaring:
class Sample {
  constructor() {
    this.x = _Sample_x(this.x);
  }
}
var _Sample_x = function(_) { return _; };
_Sample_x = inject(_Sample_x);

Of course, this approach has the obvious downside of allocating a function for every argument/field that has a decorator.