Decorators vs Annotations (was April 10 2014 Meeting Notes)

Ron Buckton Ron.Buckton at microsoft.com
Tue Apr 15 10:06:08 PDT 2014


> -----Original Message-----
> From: Erik Arvidsson [mailto:erik.arvidsson at gmail.com]
> Sent: Tuesday, April 15, 2014 8:38 AM
> To: waldron.rick at gmail.com; es-discuss at mozilla.org; Yehuda Katz
> Cc: Ron Buckton
> Subject: Decorators vs Annotations (was April 10 2014 Meeting Notes)
> 
> On Tue Apr 15 2014 at 10:27:23 AM, Rick Waldron <waldron.rick at gmail.com
> <mailto: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
> 
> 	```js
> 	@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:
> 
> ```js
> function doSomething(@inject xhr) {
>   ...
> }
> ```
> 
> With decorators it could course be rewritten as:
> ```js
> @annotateParam(inject, 'xhr')
> 
> function doSomething(@inject xhr) {
>   ...
> }
> ```
> 
> Maybe that is sufficient? Maybe that could be done as syntactic sugar?

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

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

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

```js
// 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: https://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:
 
```js
// 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):

```js
// 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.

Ron


More information about the es-discuss mailing list