Decorators for functions

# Axel Rauschmayer (9 years ago)

wycats/javascript-decorators/blob/master/README.md

The decorator proposal does not include decorators for functions, because it isn’t clear how to make them work in the face of hoisting.

However, it would be great to have them. I see two possible solutions:

– A decorator on a function declaration prevents hoisting.

– Enable decorators for function expressions, arrow functions and generator function expressions.

Does either one of those make sense?

Axel

# Andrea Giammarchi (9 years ago)

You haven't provided a single use-case example, like how are you going to decorate a function or why.

IMO if implemented it will be incompatible with non ES6 code unable to distinguish between classes and functions unless fully transpiled, making decorators less portable.

One thing I like about current state is that you can use decorators even in ES5 browsers [1]

Just my 2 cents,

[1] as shown in the second example of the universal mixin module WebReflection/universal-mixin#universal-mixin-

# Bob Myers (9 years ago)

So wait, you agree there are valid use cases for decorating functions when they are methods on an object (although I don't see much along that line in the proposal). But if the function is "stand-alone" suddenly the use cases evaporate?

For example, I hack on and off on a framework involving transforming functions into self-updating versions of themselves. Of course I can write this as

self_updatify(function a() { })

but it would be more compact and readable to say

@self_updatify
function a() { }

Which is, please correct me if I'm wrong, all decorators are about. To take one example, Ember wants to use decorators not to get new functionality, but to make the writing of computed properties less ugly, among other reasons. (The fact that Ember makes little use of non-method functions may also be one reason for the low priority placed on figuring out how to decorate functions.)

We can work to develop more examples and motivations and use cases for decorated functions, although frankly it seems a little odd, as I mentioned above, that there could be compelling use cases for decorated methods but not for decorated functions. For the purposes of this discussion I will stipulate that having decorated functions is an idea worth pursuing. If you disagree, there's not point in reading further (but you might want to stop and ask yourself why if it's such a great idea to have decorated methods, no-one will ever want decorated functions).

The only problem as far as I am aware is how to handle hoisting.

AFAICS, hoisting is not an issue if the decorator has no side effects. Of course there is nothing wrong with writing decorators with side effects, and there are valid use cases for doing so, but they are rare. Furthermore, even if a decorator does have side-effects, in only some subset of such cases will the side effects together with hoisting result in unexpected behavior.

So to say that we will simply give up on decorated functions because of the few cases where decorators have side effects, and those side effects cause unexpected behavior due to hoisting, is really throwing out the baby with the bathwater. We are telling people that you cannot decorate functions at all, ever, or to decorate functions they must wrap them in a class or object, because of some potentially unexpected behavior in what is decidedly an edge case.

Various proposals have been made on this topic, including hoisting separately from decorating, hoisting and decorating at the same time, change hoisting behavior for decorated functions, etc. etc. Each of these ideas has its proponents and those who think it is the work of the devil. I will not go into the details of these approaches here, and to do so is actually a bit above my pay grade.

I would just say that it is odd in the extreme that a group of world-leading language designers would just throw in the towel when confronted with a pretty small roadbump, instead of figuring out ways to solve it. The alternative, which is to implement decorators only for methods and classes and leave out functions because we couldn't figure it out, seems like a major admission of failure.

Bob

# Andrea Giammarchi (9 years ago)

You completely misunderstood me Bob, I don't think there's any valid use case for functions at all, including methods and ... .specially methods!!!

I was thinking about decorators for classes, when you enrich their prototype in case the decorator receives a function instead of an object, or you enrich the object in every other case.

You transform at runtime prototype methods??? Good for you, but that's something I'd never do, unless we are talking about lazy evaluation on the instance, where I don't see how lazy evaluation for an inherited method has anything to do with functions decorators.

The difference is huge, methods will most likely have a this reference to be promoted on eventually, in the other case you have a function that unless its body has "switches" can cannot really promote much by itself and passing it around as higher order function that mutates? ... yak!

As summary: does anyone has a valid use case for a generic function decorator? 'cause I still don't see one, and having decorators for any sort of function would be problematic in terms of code portability, which is all I am saying.

# Matthew Robb (9 years ago)

Why not just do:

const {myFunc} = {
  @someDecorator;
  myFunc() {

  }
};
  • Matthew Robb
# Andrea Giammarchi (9 years ago)

FWIW that would make code portable, accordingly with current proposal, the object is received, the descriptor and its name are clear. I personally wouldn't mind that pattern, yet I'm curious about use cases for functions decorators.

# Andreas Rossberg (9 years ago)

Drive-by-comment:

On 20 October 2015 at 14:40, Bob Myers <rtm at gol.com> wrote:

AFAICS, hoisting is not an issue if the decorator has no side effects.

Not so. Initialisation order is another issue. Consider:

var x = 0 @bla(x) function f() {}

Also, as a side note, pretty much everything in JavaScript potentially has side effects.

# Kevin Smith (9 years ago)

I would just say that it is odd in the extreme that a group of world-leading language designers would just throw in the towel when confronted with a pretty small roadbump, instead of figuring out ways to solve it.

Another drive-by...

The trick is introducing new features without exploding the number of rules which must be remembered in order to use that feature.

# Eli Perelman (9 years ago)

More drive-bys.

I could see decorators as a nice way to define "functional" behavior for generic functions:

@curried var add = (a, b) => a + b;

@memoize var fetch = (url) => /* logic */;

Eli Perelman

# Frankie Bagnardi (9 years ago)

Decorators can be both used to wrap things and to annotate them. For example, here we're setting a flag with the web.method function which is used by by this fictional 'web' framework. The others are used as middleware that modify the function arguments at each step.

export default
@web.method('post')
@web.parsesJSON()
@web.expectsBody({
  emailAddress: String, password: String,
})
function handleLogIn(req, res, body){
  // ...
}

In this example we have a React component that's just a simple function. We want to wrap it with a high order component.

@providesSomething()
function MyComponent({something}){
  // ...
}

Here we're using dependency injection:

@di.provide(Calculator)
function add(calc, a, b){
  return calc.add(a, b);
}

add(1, 2) === 3

I'm not completely sold on if these are good ideas. It might be more confusing than it's worth.

# Andrea Giammarchi (9 years ago)

If you don't pass the surrounding object / prototype I don't personally see any added value from using fn.bind(null, Calculator) or simply dl.provide(Calculator, function add(calc, a, b) {})

Specially the export example would make my eyes bleed once the export is inside curly brackets: I've instantly no idea if I'm exporting an object through decorators and methods or just functions.

True that methods won't have the function bit but we're back to the portability issue I've mentioned, which is not just a bump in the road, it's a lock in ES6 only, transpiled or not, which reminds me issues with modules between Python 3 and 2 ... I wish we'd never go even close to that situation with decorators, which are powerful and right now portable.

Are we really willing to lose current portability considering there is a simple work around and even the only dev with concrete examples is not sold it's a good approach rather than just a more confusing one?

Wouldn't be wise to eventually bring in current proposal and reserve eventually the function bit for 2017 so that the migration to the new decorator feature would be less problematic?

After all I don't see any problem in implementing the idea later on so that going out with just current proposal is past and future friendly.

Again just my thoughts

# Isiah Meadows (9 years ago)

I would have to agree with Andrea here. I don't see any benefits decorated functions, e.g. @memoize function inc(x) { return x + 1 } would provide other than syntax over const inc = memoize(x => x + 1).

# Bob Myers (9 years ago)

You completely misunderstood me Bob.

Sorry about that.

I was thinking about decorators for classes, when you enrich their

prototype in case the decorator receives a function instead of an object, or you enrich the object in every other case.

Am I understanding this right that you are thinking of decorators as a type of trait mechanism?

As I understand it, decorators are pure sugar. I like sugar as much as the next guy, as long as it addresses common use cases, helps us write more readable, maintainable code, and doesn't eat too much into future syntax space. But I am on the fence about decorators.

It's worth noting that some kinds of decorators could possibly be handled via :: syntax:

(function() { })::decorate_me()

However, it is worthwhile considering the Ember "computed property" use case. For those who don't know Ember, computed properties recompute themselves on demand when their dependencies change. This requires the dependencies to be declared somehow. Then, the dependencies need to be retrieved again within the function.

The classic syntax for writing computed properties is:

foo: Ember.computed('dep1', 'dep2', function() { return this.get('dep1') +
this.get('dep2'); })

An alternative in widespread use is hardly prettier, and requires polluting the function prototype:

foo: function() { return this.get('dep1') + this.get('dep2');
}.computed('dep1', 'dep2')

The reason the Ember folks latched on to decorators is because they want to write this:

@computed('dep1', 'dep2')
foo(dep1, dep2) { return dep1 + dep2; }

and that is indeed much better. To accomplish this, the computed decorator would be roughly written as

function computed(...deps) {
  return function(target, name, descriptor) {
    var undecorated = descriptor.value;
    descriptor.value = undecorated.apply(this, deps.map(dep =>

this.get(dep));
    return descriptor;
  };
}

The above is meant merely for illustrative purposes.

The sweetness of the resulting sugar should attract a fair number of flies.

-- Bob

# Andrea Giammarchi (9 years ago)

Bob

Am I understanding this right that you are thinking of decorators as a

type of trait mechanism?

absolutely, which is what I've linked at the beginning ( universal-mixin module that already works with "ES7" proposed decorator syntax down to ES3 )

Your last example

@computed('dep1', 'dep2')
foo(dep1, dep2) { return dep1 + dep2; }

it's about prototype/object method decoration, which is OK as use case 'cause it sends the object/prototype as a target, the name 'foot' and the descriptor with the function as its value.

I'm absolutely fine with that because it's contextual. If you have only the function without a target though ... no that is the problem I am talking about, and it's purely related to portability of the proposal itself: right now is just sugar with a context (class/object), if we have to consider functions as functions, not as method and not as constructors (ES5/3) then we have a portability problem and it would be a pity because decorators are a wonderful migration pattern, as they are proposed now.

Add functions and goodbye portability in non transpiled code.

Hope it's clear what is my (actually) only concern in introducing extra specifications for functions only: few benefits, possibly more code to write, broken backward portability for pure /clean environments that don't want/have/use transpilers (nodejs, micro controlelrs, espruino, Yun etc)

Best

# Ron Buckton (9 years ago)

I can think of numerous examples of how decorators could apply to functions, and I’ve added them to a gistgist.github.com/rbuckton/37e944f7986e6833949e [1] for easier consumption. It’s true that simple decorators for functions can work as simply as function calls, but this becomes a bit unwieldy if you need to compose multiple decorators on a single function.

Consider a scenario combining decorators providing runtime type information as an annotation with one that adds runtime type checking. With decorators this might be:

@paramtypes(() => [Number, Number])
@returntype(() => Number)
@checked
function add(x, y) { return x + y }

If I just use function expressions, this is feasible if a bit awkward:

const add =
    paramtypes(() => [Number, Number])(
        returntype(() => Number)(
            checked(
                function (x, y) { return x + y; })))

It feels a bit developer-hostile to have to rebalance parentheses if you want to add a decorator, and there are ASI hazards if you misplace an opening paren. Also, you can no longer infer the function name “add” from the const binding.

Using :: isn’t a great replacement either, as there are many hazards such as:

// The following means “call `decorator` with `this` bound to the function object”.
// Also, it becomes impossible to infer the function name “a” from the let binding.
let a = function() { }::decorator(x)

let b = function() { }
::some.decorator(x) // ASI hazard as `::` can be either prefix or infix.

One of the initial drivers for decorators was class methods, as there’s no expression context immediately inside the class body in which you can use either of the above scenarios. This necessitated a declarative form for decorators to allow these scenarios to exist. Having parity across class methods, classes, and functions (of all kinds) presents a more consistent story to developers. The upswing in decorator use in both TypeScript and Babel has been very positive, with libraries like Angular leveraging decorators heavily in their codebase. Since we introduced decorators into TypeScript, we’ve had a fair bit of feedback requesting support for function decorators.

I do think function decorators should wait until the Class/Property decorators proposal advances further along the standards track. Axel’s initial concerns/questions around hoisting are valid and there isn’t a clear consensus on the semantics for functions. That said, I’ve been mostly in the camp of introducing TDZ for function declarations that have decorators. Decorators are a new syntactic form and we have the opportunity to communicate this caveat with the development community by the time the feature lands. It seems easy enough to explain that:

@decorator
function func() { }

Is the equivalent of:

let func = @decorator function() { }

Introducing TDZ allows to generally warn early as part of the static semantics, so developers won’t fall into a well with respect to adding a decorator to a function and not being able to quickly understand how that change affects the behavior of their code.

I’m not certain what the current plan of record is, but the best approach may be:

  1.   Advance and get consensus on the Class/Property decorators proposal
    
  2.   Draft a separate proposal for decorators on function expressions, generator function expressions, and arrows
    
  3.   Draft a separate proposal for decorators on function declarations
    

Steps 1 and 2 above shouldn’t be significantly difficult and don’t necessarily introduce any major new semantics outside of the decorators themselves. Step 3 covers a thornier issue as it not only introduces the new semantics of decorators but also introduces side-effects due to hoisting.

Ron

[1] gist.github.com/rbuckton/37e944f7986e6833949e

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Andrea Giammarchi Sent: Tuesday, October 20, 2015 3:34 AM To: Axel Rauschmayer <rauschma at icloud.com>

Cc: es-discuss mailing list <es-discuss at mozilla.org>

Subject: Re: Decorators for functions

You haven't provided a single use-case example, like how are you going to decorate a function or why.

IMO if implemented it will be incompatible with non ES6 code unable to distinguish between classes and functions unless fully transpiled, making decorators less portable.

One thing I like about current state is that you can use decorators even in ES5 browsers [1]

Just my 2 cents,

[1] as shown in the second example of the universal mixin module WebReflection/universal-mixin#universal-mixin-na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FWebReflection%2Funiversal-mixin%23universal-mixin-&data=01|01|ron.buckton%40microsoft.com|50c27148ba3543c448f608d2d939f7c4|72f988bf86f141af91ab2d7cd011db47|1&sdata=T1q%2BKVVIyc%2BNxSyG9Ri%2BmNAMPqq3p6Ydofoe1WQrg5U%3D

On Tue, Oct 20, 2015 at 10:30 AM, Axel Rauschmayer <rauschma at icloud.com<mailto:rauschma at icloud.com>> wrote:

wycats/javascript-decorators/blob/master/README.mdna01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fwycats%2Fjavascript-decorators%2Fblob%2Fmaster%2FREADME.md&data=01|01|ron.buckton%40microsoft.com|50c27148ba3543c448f608d2d939f7c4|72f988bf86f141af91ab2d7cd011db47|1&sdata=2fTFCt8Rrkx6pTvFLkcl8xfV5EKny35QpLPVTmm0aV8%3D

The decorator proposal does not include decorators for functions, because it isn’t clear how to make them work in the face of hoisting.

However, it would be great to have them. I see two possible solutions:

– A decorator on a function declaration prevents hoisting.

– Enable decorators for function expressions, arrow functions and generator function expressions.

Does either one of those make sense?

Axel

-- Dr. Axel Rauschmayer axel at rauschma.de<mailto:axel at rauschma.de>

rauschma.dena01.safelinks.protection.outlook.com/?url=http%3A%2F%2Frauschma.de&data=01|01|ron.buckton%40microsoft.com|50c27148ba3543c448f608d2d939f7c4|72f988bf86f141af91ab2d7cd011db47|1&sdata=Vclw%2FyI29j4WqkHWjTgaZtoL12pnJNCWYBJK4wTYVA0%3D

# Jonathan Bond-Caron (9 years ago)

On Tue Oct 20 05:30 AM, Axel Rauschmayer wrote:

The decorator proposal does not include decorators for functions, because it isn’t clear how to make them work in the face of hoisting.

What's the obsession with decorators?

Decorators are like saying everyone can decorate their Christmas trees. That's nice once a year but not when you start looking at all the different Christmas trees and have to maintain that stuff.

Suddenly the single language you thought you understood has many dialects & philosophies. Aren't embeddable languages more interesting to learn then decorated trees?

# Alexander Jones (9 years ago)

I've become convinced by this thread that we don't need this. Other languages where decorators are useful and prevalent don't have the expressivity JS has, particularly regarding dynamism and function expressions. JS class is an awkward case due to not supporting non-method members, but I think that is one of the actual problems that should be solved.

Another major point I have is that most of the reasoning for decorator syntax is actually generic reasoning for paren-free function invocation à la Perl, Ruby, CoffeeScript, etc. Let's talk about that, instead of building unilateral syntax extensions into the language.

# Andrea Giammarchi (9 years ago)

Again, everything can be defined in a similar way, actually granting those function cannot posibly be declared or redefined differently, being constants.


const {
  assert,
  log,
  add
} = {
  @conditional(DEBUG)
  assert(condition, message = "assertion failed.") {
    if (!condition) throw new Error(message);
  }

  @conditional(TRACE)
  log(message) {
    return target => function () {
       console.log(message);
       return target.apply(this, arguments);
    };
  }

  @metadata("design:paramtypes", () => [Number, Number])
  @metadata("design:returntype", () => Number)
  function add(a, b) {
    return a + b;
  }

};

Beside that, that gist is completely unreadable to my eyes, I guess it would take some time to be maintained as well if that was production code.

The work around fixes all that, it keeps portability of the current proposal, and it ensure log will always be that log and nothing else in that very same scope + strawberry on top, less writing and always named functions.

How cool is that?

Best

# Alexander Jones (9 years ago)

Although this works, it seems like a bit of a violation of say-what-you-mean and dont-repeat-yourself to me. You have to write the name of each function twice, and you are defining a shorthand object literal just for the sake of unpacking it.

If we must have syntax for this, I'd propose hijacking @ to mean general paren-free invocation, using the same precedence rules as Coffee:

const fibonacci = memoize(function(n) {...});
const fibonacci = @memoize function(n) {...};
const fib100 = @fibonacci 100;
@window.alert "If this syntax works for functions, why not let it work for
anything?";

But I must ask - why exactly do people have a problem with the extra brackets again? Is it really just because we don't have good paredit or other tree-based editing for JS yet? (Or do we?)

# Andrea Giammarchi (9 years ago)

I agree it's not a "must have", considering what we lose if applied (portability) + you are back to broken portability with @meomize function ... VS meomize(function)

So, I think portability is more important than some edge case without parenthesis around and if really wanted as a pattern,there is a workaround.

Create an object to unpack it ... why not ... named functions? why not ... I'm used to write {method: function method() {}} for debugging sake and I like named functions but going even more dirty with the workaround maybe an array to unpack would make it less maintainable but less verbose too.

It will still be portable, which is all it matters to me.

# Yongxu Ren (9 years ago)

I agree with Andrea, basically nothing after es3 is a "must have", if we apply the same argument.

I see a lot possibilities of decorator, I think it would be great if it can be used on any lvalue, or even statements.

some of the application I can think of:

//static type checking
@type(number,number,number)
function f(a,b){
        ....
}

@debug(unitTestForFunction)
let f = (x) => .....

@parallel
for(.......){.......}

I think the best part of decorator is it can be processed by compiler or polyfilled, so we can use them to instruct translator such as babel, to do extra checking during development and drop them in production. It can also be used by JET, if supported. Extra type checking, debugging can be ignored by native engine, polyfilled to function that does nothing(as fallback if no native support), or optimized as static typed function for performance.

I'd love to see a limitless decorator that also allows browser/compiler to receive hints from developers.

# Alexander Jones (9 years ago)

Why stop at ES3?

Point is this literally saves you two parens in exchange for one @ and a peculiar logical insertion after the = for the use cases discussed.

Let's please collectively remember and acknowledge that this exists in Python because they have no function literal syntax. We do.

# Andrea Giammarchi (9 years ago)

last time I say this:

(function () {'use strict'; class A {} console.log(A); } ());

That is function A in Firefox nightly, and for some reason function class A in Chrome but it would be function A in every other browser.

There is no way to distinguish between function A and just a regular function, meaning this will break portability and make life hard for using the decorator with classes prototypes instead of of just functions.

The capitalized name is just a convention, not a standard defined constrain ... are you willing to lose this because you cannot use an already working workaround or two parenthesis instead of a @ char ?

If yes, why if I might ask? It hasn't been even a concern 'till this thread now it's suddenly something that must be out?

# Yongxu Ren (9 years ago)

Alex,

const fibonacci = memoize(function(n) {...});

this is actually a very good example that why we should let decorator do the job.

Regardless the name memoize or memorize, whatever,

How can you tell if fibonacci is a function, a list, or just a number?

That is why we should allow decorator on function

# Rick Waldron (9 years ago)

Or just use call constructor:

class F { #decorator call constructor() { ... } }

# Jordan Harband (9 years ago)

One thing that seems to be missing from this thread is acknowledgement that decorators are not just simple function wrappers. They take a property descriptor as an argument, and they can return a new property descriptor - which allows an object or "class" to have its shape determined at creation time, allowing for massive engine optimization, as well as allowing for things like enumerability, configurability, writability, getters, setters, and various other metadata to be determined before the object in question is mutated, or even exists.

Decorators are certainly something that can be quasi-polyfilled, but when applied to object or "class" properties, it allows access to the property descriptor without needing to assign, then reflect, and then define the resulting descriptor. @foo isn't merely sugar for a function call without parens - it's an imperative way to define property descriptors at object initialization time, which is something that does not currently exist in the language beyond get foo() {} and set foo() {}.

If this has been mentioned and I missed it, please ignore me, but the thread seems to have overlooked this facet of the proposal. Also, if I'm reading the proposal incorrectly, please correct me!

# Isiah Meadows (9 years ago)

I think the main debate here is ergonomics. And by the way, since most descriptors simply replace the value property (especially most that are generic functional utilities), it could just as easily be made to deal with both. I already make most of my descriptors that way now, so they can be applied to either a class or function.

function descriptor(f) {
  return function (target, name, desc) {
    if (arguments.length < 3) return f(target)
    desc.value = f(desc.value)
  }
}

const memoize = descriptor(f => function () {
  // stuff
})

const fibs = memoize(x =>
  x < 0 ? 0 :
  x <= 1 ? x :
  fibs(x - 1) + fibs(x - 2))

class Foo {
  @memoize
  method(x) { /* ... */ }
}

My personal opinion is that the decorator looks better than function application. It also reads more clearly in that it's a function modifier, not a function simply taking a function value (like Array.prototype.map or Promise.prototype.then).

Python function decorators work similarly, IIRC.

@decorator
function memoize(f) { /* ... */ }

@memoize
function fibs(n) {
  if (x < 0) return 0
  if (x <= 1) return x
  return fibs(x - 1) + fibs(x - 2)
}
# Alexander Jones (9 years ago)

OK, appreciate that. Let's explore this problem space: Why not have syntax to set a property descriptor onto a class or object literal?

In Python, this is implicit. For example (IIRC):

class Foo:
    prop = some_descriptor
    x = 100

Both Foo.x and Foo().x (instance attribute access) have the value 100, but assuming some_descriptor has __get__, __set__, __delete__ methods, then accessing prop on an instance will magically use these descriptor methods. Obviously an issue here is that you might literally have wanted the prop attribute's value to be this actual descriptor object, so IMO something explicit for this is better in JS.

Let's instead imagine that @ is used to activate descriptor setting:

const myObj = {
    prop: @someDescriptorExpression,
};

// e.g.
const myObj = {
    prop: @{value: whatever, writable: false},
};

Now clearly "decorators" like readonly are just function application. Even defining a get/set pair for a property is straightforward:

const readonly = v => {value: v, writable: false};

const myObj = {
    prop: @readonly(whatever),
    answer: @readonly(42),
    x: @{
        get() { return this.answer(); }
        set(x) { throw new Error("nope"); }
    }
};

"Decorators" like memoize are still just in terms of functions and don't require this syntax -- memoizing something should never be in terms of descriptors because it has zero interest in property descriptors (why should memoize make a call on whether the returned function is an enumerable, or writable, etc.?)

const fib = memoize(x => {
    // ...
});

ES6 class issues are IMO an orthogonal issue to be solved:

class Foo {
    x: 100  // lots and lots of questions here...
}
# Andrea Giammarchi (9 years ago)

Agred with Jordan and it's basically what I've said since the beginning. Decorators with target and descriptors are fine and works well, you drop targets and descriptors from the equation, it's an ugly mess that canno be ported and every decorator will magically decide if it should work with a class rather than a function.

Maybe for functions only we need something else, which might work as well. @@ or @() or @::meomize or something

# Andreas Rossberg (9 years ago)

On 22 October 2015 at 06:34, Jordan Harband <ljharb at gmail.com> wrote:

One thing that seems to be missing from this thread is acknowledgement that decorators are not just simple function wrappers. They take a property descriptor as an argument, and they can return a new property descriptor - which allows an object or "class" to have its shape determined at creation time, allowing for massive engine optimization,

Er. I don't know where this myth is coming from, but let me debunk it right there:

Decorators do not enable optimisations. If anything, they prevent optimisations.

What enables optimisations is a more declarative semantics with more invariants, less mutation, less reflection, and less intercession. That was one significant advantage of class syntax over previous imperative JS patterns.

Decorators pretty much revert that (hard-fought) progress, because despite their looks, they are all but declarative, and just as imperative as the old-style patterns. Or function wrappers. A class that uses decorators will very likely have to go through all the same (or even worse) runtime overhead.

# Jonathan Bond-Caron (9 years ago)

On Thu Oct 22 07:44 AM, Andreas Rossberg wrote:

determined at creation time, allowing for massive engine optimization,

Ya I'm not sure from which hat "massive engine optimization" comes from?

What's meant is likely using decorators as annotations (compile time optimizations hints): www.google.com/patents/US7013458

Or 'ambient decorators': jonathandturner/brainstorming/blob/master/README.md#c6-ambient-decorators

There's 2 patterns (maybe more?): (a) Tagging a 'tree transformation' on a node. (b) Metadata at compile time on a node.

The thing about (b) is it can easily live outside of the code (like in typescript where you have an optional header/declaration file)

With (a), it seems more conservative to see how it gets used with classes before bolting on to functions (opinion: end result in java is not something to be proud of).

# Jordan Harband (9 years ago)

Andreas, thanks for correcting me on the optimization angle. I've been under that impression for awhile.

Are you saying that to achieve the optimization I envision, we'd need declarative syntax for descriptor properties (like enumerability etc), rather than function calls? or would that also prevent optimizations?

# Yongxu Ren (9 years ago)

I don't think

@@ or @() or @::meomize

would really help much, you can tell what the decorator does by simply looking at its name. And looks like you can not use @ and @@ for the same decorator function without adding extra condition checking inside the function.

There are two patterns that we have discussed here, they are actually quite distinct. I still think we should support decorator on variables, but maybe we should have 2 distinct syntax for the normal decorators and "ambient decorators"(from Jonathan's post):

  1. decorator that alter the code behavior, the currently proposed decorator. Such as @memoize

  2. decorator that absolutely does not alter the code behavior, only used for optimization, checking or debugging. Instead of @, a distinct syntax will be much clearer ex.@annotatition@ (Maybe it should be called annotation instead?):

@deprecated@

@number,number=>string@/*type checking*/

@debug("this message will only print in development mode")@

it sounds like terrible idea to have a decorator in code that you can not figure out if it will alter the code behavior by looking at it. I do like to see all the new ideas been added into javascript, but it is also necessary to eliminate the ambiguity whenever possible.

# Andrea Giammarchi (9 years ago)

Removing ambiguity is my point since the very first post: current proposal is about a target, a property name, and a descriptor for such property ... having functions/variables decorators have no target (in strict mode undefined can't be a target, right?) and not necessarily a descriptor, or if any, always a data one with fields that makes no sense (like enumerable within a private scope ... what does that even mean)

I'm all in for a distinct, separate, syntax to decorate "callables" or other variables as long as the current proposal will make for ES7 and won't be bothered by this different requirement.

# Ron Buckton (9 years ago)

Andrea,

Is your concern about disambiguating the usage of a decorator at the call site or within the body of the decorator? In the current proposal, you can decorate a class member, or the class itself.

When decorating a class member, three arguments are passed to the decorator: The target, the property key (string or symbol), and the descriptor. When decorating the class, one argument is passed to the decorator: The constructor function. Generally this means that you can disambiguate the usage of the decorator based simply on arguments.length, or testing for undefined for the property key or descriptor.

Would it be better to have a more consistent way to disambiguate the usage of a decorator from within the decorator? This could be addressed with something like a Reflect.getDecoratorUsage API or a function.decoration meta-property or the like. Consider something like:

function memoize(target, key, descriptor) {
  switch (Reflect.getDecoratorUsage(arguments)) {
    case "class":
      // `target` is the class constructor. `key` and `descriptor` are undefined.

    case "function":
      // `target` is the function. `key` and `descriptor` are undefined.

    case "method":
      // `target` is the object containing the method (e.g. constructor
      // for a static method, prototype for a prototype method, or
      // instance for an object literal method).
      // `key` is the string or symbol property name for the method.
      // `descriptor` is the property descriptor for the method.

    case "accessor":
      // `target` is the object containing the accessor (e.g. constructor
      // for a static accessor, prototype for a prototype accessor, or
      // instance for an object literal accessor).
      // `key` is the string or symbol property name for the accessor.
      // `descriptor` is the property descriptor for the accessor.
  }
}

Ron

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Andrea Giammarchi Sent: Thursday, October 22, 2015 10:47 AM To: Yongxu Ren <renyongxu at gmail.com>

Cc: es-discuss mailing list <es-discuss at mozilla.org>

Subject: Re: Decorators for functions

Removing ambiguity is my point since the very first post: current proposal is about a target, a property name, and a descriptor for such property ... having functions/variables decorators have no target (in strict mode undefined can't be a target, right?) and not necessarily a descriptor, or if any, always a data one with fields that makes no sense (like enumerable within a private scope ... what does that even mean)

I'm all in for a distinct, separate, syntax to decorate "callables" or other variables as long as the current proposal will make for ES7 and won't be bothered by this different requirement.

On Thu, Oct 22, 2015 at 6:29 PM, Yongxu Ren <renyongxu at gmail.com<mailto:renyongxu at gmail.com>> wrote:

I don't think

@@ or @() or @::meomize

would really help much, you can tell what the decorator does by simply looking at its name. And looks like you can not use @ and @@ for the same decorator function without adding extra condition checking inside the function.

There are two patterns that we have discussed here, they are actually quite distinct. I still think we should support decorator on variables, but maybe we should have 2 distinct syntax for the normal decorators and "ambient decorators"(from Jonathan's post):

  1. decorator that alter the code behavior, the currently proposed decorator. Such as @memoize

  2. decorator that absolutely does not alter the code behavior, only used for optimization, checking or debugging. Instead of @, a distinct syntax will be much clearer ex.@annotatition@ (Maybe it should be called annotation instead?):

@deprecated@

@number,number=>string@/*type checking*/

@debug("this message will only print in development mode")@

it sounds like terrible idea to have a decorator in code that you can not figure out if it will alter the code behavior by looking at it. I do like to see all the new ideas been added into javascript, but it is also necessary to eliminate the ambiguity whenever possible.

On Thu, Oct 22, 2015 at 11:20 AM, Jonathan Bond-Caron <jbondc at gdesolutions.com<mailto:jbondc at gdesolutions.com>> wrote:

On Thu Oct 22 07:44 AM, Andreas Rossberg wrote:

determined at creation time, allowing for massive engine optimization,

Ya I'm not sure from which hat "massive engine optimization" comes from?

What's meant is likely using decorators as annotations (compile time optimizations hints): www.google.com/patents/US7013458na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.google.com%2Fpatents%2FUS7013458&data=01|01|ron.buckton%40microsoft.com|4f28552d1837468197db08d2db08dcea|72f988bf86f141af91ab2d7cd011db47|1&sdata=atTz2em0YIlXUvUVgiWsZ5xKNjJsIOzm3wLejdDl33E%3D

Or 'ambient decorators': jonathandturner/brainstorming/blob/master/README.md#c6-ambient-decoratorsna01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fjonathandturner%2Fbrainstorming%2Fblob%2Fmaster%2FREADME.md%23c6-ambient-decorators&data=01|01|ron.buckton%40microsoft.com|4f28552d1837468197db08d2db08dcea|72f988bf86f141af91ab2d7cd011db47|1&sdata=1aXPAnWuPpVgE3wCYZqcEnTaksCKqzwuq0bGLkkG8Uo%3D

There's 2 patterns (maybe more?): (a) Tagging a 'tree transformation' on a node. (b) Metadata at compile time on a node.

The thing about (b) is it can easily live outside of the code (like in typescript where you have an optional header/declaration file)

With (a), it seems more conservative to see how it gets used with classes before bolting on to functions (opinion: end result in java is not something to be proud of).

# Andrea Giammarchi (9 years ago)

Ron, there's no way you can distinguish a class from a generic function in current specifications.

Having one argument won't tell me much, having a way to know that is not a class I need to decorate (traits/mixins) but just a function, so ignoring its prototype and do something else, would be cool but it's unfortunately not possible or portable.

How would you distinguish between a class or a function for a generic decorator? Or all you are saying is that decorators shouldn't be able to distinguish at all between a class, rather than a function?

# Salvador de la Puente González (9 years ago)

After reading the conversation, I think there is no ambiguity at all or, may be, it must be there: decorating a function should be like decorating a class, you can not distinguish between them and that's all. Just look at the code generated in Babel: babeljs.io/repl/#?experimental=true&evaluate=true&loose=false&spec=false&code=%40decorator class A {}

You'll see:

A = decorator(A) || A;

And this is the traditional notion of decorator we see in Python and other languages. A simple way to check for a generic decorator would be:

function decorator(target, name='', descriptor=null) {
  if (descriptor) console.log('Decorating a member');
  else console.log('Decorating either a function or class');
}

And it's very consistent if you think the only difference a ES6 class introduces is that you are not allowed to call the class as a function.

So, the generated code for:

@decorator
function A() { }

Should be:

function A() {}
A = decorator(A) || A;

And that's all, if you always add the overwrite after the definition, hoisting is irrelevant but if it worries you, simply avoid hoisting when decorating as Axel suggested.

PS: Well thought, it must be possible for an hypothetical reflection function to determine if a function is a class or not as classes are marked to throw when they are not invoked with new.

# Andrea Giammarchi (9 years ago)

which one is true?

I think there is no ambiguity at all ... decorating a function should be

like decorating a class

'cause I think those are very different things. You gonna wrap for sure the first one, you most likely ever even bother replacing the class, rather enriching its prototype or its public statics.

Maybe it's me overlooking at this, but the fact we cannot distinguish between classes and functions doesn't feel right to me.

P.S. babel has some target (browsers mostly, and nodejs) but it shouldn't be the reason to choose a pattern instead of another, or at least not the only one

# Ron Buckton (9 years ago)

-----Original Message----- From: Andrea Giammarchi [mailto:andrea.giammarchi at gmail.com] Sent: Thursday, October 22, 2015 12:53 PM

Ron, there's no way you can distinguish a class from a generic function in current specifications.

Yes, this is true. However, decorators aren't in the current specification either. If this becomes a must-have then we should investigate an API or meta-property that could expose this information, as I mentioned in my previous reply.

Having one argument won't tell me much, having a way to know that is not a class I need to decorate (traits/mixins) but just a function, so ignoring its prototype and do something else, would be cool but it's unfortunately not possible or portable.

How would you distinguish between a class or a function for a generic decorator? Or all you are saying is that decorators shouldn't be able to distinguish at all between a class, rather than a function?

While I think it would be a valuable feature to be able to distinguish between a class and a function, it may make it difficult to properly reason between an ES6 class and an ES5/3 function-as-a-class-constructor. The possibility of adding a "call constructor" further complicates this.

It's useful to be able to disambiguate within the decorator, so I can know whether I would need to maintain a prototype chain:

function someDecorator(target) {
  // using "function.decoration" as a possible meta-property
  if (function.decoration === "class") {
    return class extends target {
      constructor(...args) {
        // do something in constructor...
        super(...args);
      }
    };
  }
  else if (function.decoration === "function") {
    return function(...args) {
      // do something in function...
      return target(...args);
    };
  }
}

Alternatively, it would be interesting if all class declarations were implicitly given a Symbol.class property on the constructor declaration. This way older function-as-a-class-constructor implementations could opt-in to stating "I'm a class":

function someDecorator(target) {
  if (arguments.length !== 1) throw new TypeError(); // only for class/function
  if (target.hasOwnProperty(Symbol.class)) {
    // class
  }
  else {
    // function
  }
}

// up-level
@someDecorator
class ES6Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

// down-level
function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point[Symbol.class] = true;
Point = someDecorator(Point) || Point;

Ron

# Andrea Giammarchi (9 years ago)

It's all OK to discuss this for the future, meanwhile there won't be a way to tell older engines the difference, which is the portability concern I've been talking about.

I guess thought it would be very misleading to have decorators that accepts both classes and regular functions, so maybe the problem, while it exists from reflection point of view, won't bother real-world code and I'm just overlooking at this.

Best

# Yongxu Ren (9 years ago)

Andrea,

My point wasn’t to implement something that allows the decorator function to figure out the difference between class and function. The point is to be able to show the difference in your code, so that it can tell the developer what the code is doing.

Also, the [‘ambient decorator’ ](jonathandturner/brainstorming/blob/master/README.md#c6-ambient-decorators, jonathandturner/brainstorming/blob/master/README.md#c6-ambient-decorators) Jonathan mentioned is what I think why we should have a new syntax for(decorator that absolutely does not alter the code behavior, only used for optimization, checking or debugging should have different syntax from decorator that alter the code behavior).

# Yongxu Ren (9 years ago)

I think I am quite off topic, how about this solution:

If we restrict only use decorator on class

And use @annotation@ decorator that absolutely does not alter the code behavior for functions, for the ambient case?

# Salvador de la Puente González (9 years ago)

On Thu, Oct 22, 2015 at 10:34 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:

which one is true?

I think there is no ambiguity at all ... decorating a function should be like decorating a class

'cause I think those are very different things.

As far as I know, classes in ES6, apart from preventing you from calling without new (and minor subclassing details), are identical to functions.

You gonna wrap for sure the first one, you most likely ever even bother replacing the class, rather enriching its prototype or its public statics.

Well, is not as sure you want to wrap a function at all. Suppose:

// operaations.js
@priority(2)
function taskA() { doSomething(); }

@priority(1)
function taskB() { doSomething(); }

@priority(5)
function taskC() { doSomething(); }

taskManager.register(taskA, taskB, taskC);
taskManager.run(); // run taking into account priority symbol of the
functions

// taskManager.js
var priority = new Symbol();

function priority(p) {
  return function(target) { target[priority] = p; }
}

On the contrary, you would want to replace a class completely:

@singleton
class system {
  get version() { return '1.0.0'; }
  get drives() { return ...; }
}

console.log(system.version);

function singleton(target) {
  return target();
}

I'm not saying is not important to distinguish between classes and functions, I'm just saying, it is not as important and not critical for the decorator syntax. It suffices for a framework to make their classes to inherit from a custom base object with a symbol isClass set to true to allow their own decorators to distinguish between classes and functions but these are specific-implementation details. What is true is the the syntax works for regular functions and class based functions.

Maybe it's me overlooking at this, but the fact we cannot distinguish between classes and functions doesn't feel right to me.

No, sorry, maybe it's me that I can not see a harmful side effect here. Can you point me some example where not distinguishing between functions and classes would be fatal, please? Maybe this way, I (and others) understand your concerns.

P.S. babel has some target (browsers mostly, and nodejs) but it shouldn't be the reason to choose a pattern instead of another, or at least not the only one

Of course, but it is a clear indicator of semantics. It's very valuable for a language to allow its own declarative semantics to be expressed in a programmatic fashion as it denotes a very consistent and versatile data & execution models.

# Andrea Giammarchi (9 years ago)

well

Can you point me some example where not distinguishing between functions

and classes would be fatal, please?

the fact a class throws once invoked like a function isn't enough fatal to you?

function invokeThat() {
  var result = Object.create(this.prototype);
  return this.apply(result, arguments) || result;
}

invokeThat.call(function sum(a, b) { return a + b; }, 1, 2); // 3

invokeThat.call(class Point{constructor(x,y){this.x=x;this.y=y;}}, 10,  15);
// Uncaught TypeError: Class constructors cannot be invoked without 'new'(…)

We are missing a Reflection.isClass ... useful or not, we've got two kind of "invokables" and nobody can distinguish from them.

# Salvador de la Puente González (9 years ago)

Well, I see, it's not worse than other dynamic typing issues (think about invokeThat.call() being called on undefined or null, the exception only gives you a runtime error and you could always wrap in a try-catch, see the message and translate to yoour own exception or recover) but that does not relate at all with decorators which is my point. I agree on Reflection.isClass although I should check the spec in the search for something related with that specific topic but returning to the previous discussion about which syntax to use when decorating functions, I don't see this is a blocker for extending present decorator syntax to functions.

# Andreas Rossberg (9 years ago)

On 22 October 2015 at 18:20, Jonathan Bond-Caron <jbondc at gdesolutions.com> wrote:

On Thu Oct 22 07:44 AM, Andreas Rossberg wrote:

determined at creation time, allowing for massive engine optimization,

Ya I'm not sure from which hat "massive engine optimization" comes from?

What's meant is likely using decorators as annotations (compile time optimizations hints): www.google.com/patents/US7013458

Note that this patent indeed defines declarative metadata annotations. That's the exact opposite of the imperative decorator proposal we are discussing right now, which completely conflates computation level and meta level and thus is pretty much unusable for compile-time hints.

# Andreas Rossberg (9 years ago)

On 22 October 2015 at 19:25, Jordan Harband <ljharb at gmail.com> wrote:

Andreas, thanks for correcting me on the optimization angle. I've been under that impression for awhile.

Are you saying that to achieve the optimization I envision, we'd need declarative syntax for descriptor properties (like enumerability etc), rather than function calls?

Yes, there would need to be a sufficient degree of phase separation, such that these annotations can be reliably gathered and inspected at compile time, and are known to be invariant at runtime.

# Andrea Giammarchi (9 years ago)

OK let me rephrase: it is not inter-operable with well known libraries that might have factories in the wild and no way to distinguish if they are dealing with new ES6 syntax or old one.

Having no way to distinguish easily breaks the web.

# Salvador de la Puente González (9 years ago)

No, sorry, I don't think so. Maybe I don't get your point so could you provide an example of how decorators on functions could break the web?

As far as I understand, if you start to use foreign code beyond its API you could break the old code with and without decorators.

# Andrea Giammarchi (9 years ago)

Salvador

could you provide an example of how decorators on functions could break

the web?

we were talking about the ability to distinguish, not anymore about decorators. I think this thread went off topic so ... let's see what TC39 will decide.