-
Notifications
You must be signed in to change notification settings - Fork 108
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
Allow decorating functions #40
Comments
One complication with function declarations is that they are hoisted - and they might be hoisted prior to where |
I guess so, but I would expect programmers to be aware of this limitation and arrange their code accordingly. |
The only way to arrange your code such that it would work is if you had |
The requirement to have decorators declared before their use on functions seems not very constraining - non-hoisted functions like Python's have the same requirement. And most decorators will probably come from third-party libraries. I feel function decorators are even more wanted than class decorators, as people are quite used to them in other languages. |
Most points I would make regarding the syntax, use cases and motivation are contained within the PEP 318 document, should that be useful. EDIT This bit is funny, we're in the exact opposite situation here:
|
As @ljharb has said here, function hoisting is the big thing which makes supporting decorated function declarations in JavaScript different, and the difference with Python is that Python doesn't have function hoisting in general. I worry that if we make the way functions are bound different when they are decorated, the effect will be confusion about how things don't quite match up. For that reason, this proposal just adds class, method and field decorators and not function decorators. |
But what about |
@iddan No, they are not, but is that a common enough idiom to warrant a language feature around? And why not just do |
Because that's the whole point of decorators - syntax sugar and clear meta-programming. You can use the same case for classes as well - why not just do An example with recompose: With Decorators@pure
@withState('data', 'setData', null)
const MyComponent = ({ data, setData }) => (
<div onClick={() => setData(Math.random())}>{data}</div>
); Without Decoratorsconst MyComponent = pure(withState('data', 'setData', null)(({ data, setData }) => (
<div onClick={() => setData(Math.random())}>{data}</div>
)); or more precisely to retain function name const MyComponent = pure(withState('data', 'setData', null)(function MyComponent ({ data, setData }) {
return <div onClick={() => setData(Math.random())}>{data}</div>
}));
Also Meta programming can be easily distinguished visually for example in: @cached
const getData = () => localStorage.getItem('data')
|
Decorator expressions cannot contain un-parenthized square-bracket property access, otherwise there would be an ambiguity with computed property names. Previously, only calls and . property access were supported. This patch also permits a property access following a call, as well as parenthized expressions. Parenthized expressions may be usfeul, for example, when automatically translating legacy decorator transpiler usage (which did not have these restrictions) into modern standard decorators. As a drive-by editorial fix, the patch also definees how to evaluate decorator expressions, by reparsing them as ordinary expressions. Closes #40
@littledan Wrong close, should be |
@littledan Could you re-open this issue? |
I'd still like to leave this feature for a follow-on proposal, due to both the issues described above and the lesser utility of it compared to class decorators (although use cases are demonstrated above). I'd encourage people who are interested in the feature to make a separate proposal repository and prototype various implementations in transpilers, to get more of data on the tradeoffs and motivation here. |
Previous discussion: tc39/proposal-decorators-previous#35 |
Two questions:
|
|
@littledan From tc39/proposals it's still stage 0, isn't it? |
The best solution for the hoisting problem I can imagine is transform @deco function f() {
funcBody
} to function f(...args) {
$do_decorate_f()
return $decorated_f.call(this, ...args)
}
var $decorated_f
function $do_decorate_f() {
if (!$decorated_f) $decorated_f = deco(function f() {
funcBody
})
}
$do_decorate_f() Aka, not hoist the execution of decorator until first call. |
Yes, I'll start a repo. |
@hax, your example only works if the decorator deals with the function's execution, but not if the decorator augments the function object itself. My opinion is that decorating a function declaration should transform it into something like this: var f = @deco function() {
funcBody
} However that means adding a decorator to a function might mean needing to move the entire function around in your code if you were depending on function declaration hoisting. Another possibility is to introduce block-scoped function declaration syntax like this: let function f() {}
const function g() {} And then only permitting decorators on a block-scoped function declaration. It still requires moving the function, but it is visually consistent with block-scoped variable declarations. A third option would be to make decorator evaluation lazy and apply it at one of two times (whichever comes first):
The closest approximation of this approach in existing code would be this: let g = f; // access f before declaration
f(); // invoke f before declaration
@deco function f() {
f(); // invoke f inside of declaration
} Becomes: let g = $$f_accessor(); // access f before declaration
(void 0, $$f_accessor())(); // invoke f inside of declaration
function f() {
f(); // invoke f inside of declaration
}
var $$f_decorated;
function $$f_accessor() {
return $$f_decorated || ($$f_decorated = f = deco(f));
}
$$f_accessor(); // eval decorator if not already accessed |
I like the third option. With a little tree cleaning this will work in any situation and usually will only require using the naïve implementation. |
@rbuckton The third option won't work with circular dependency in ES module. Given the scenario: // main.mjs
import './f';
// f.mjs
import { g } from './g';
export function f ( x ) {
return x + 1;
}
console.log(g(1));
// g.mjs
import { f } from './f';
export function g(x) {
return x - 1;
}
console.log(f(1)); Which should output When decorating // f.mjs
import { g } from './g';
function deco(fn) {
return function (x) {
return fn(x) + 100;
}
}
export @deco function f ( x ) {
return x + 1;
}
console.log(g(1)); Option a): still exports // ...
export function f ( x ) {
return x + 1;
}
var $$f_decorated;
function $$f_accessor() {
return $$f_decorated || ($$f_decorated = f = deco(f));
}
$$f_accessor();
console.log(g(1)); Then undecorated original function gets called. Option b): exports the decorated one: // ...
function f ( x ) {
return x + 1;
}
var $$f_decorated;
export { $$f_decorated as f }
function $$f_accessor() {
return $$f_decorated || ($$f_decorated = f = deco(f));
}
$$f_accessor();
console.log(g(1)); Then it would throw due to calling Option c): exports the accessor call result: // ...
function f ( x ) {
return x + 1;
}
var $$f_decorated;
function $$f_accessor() {
return $$f_decorated || ($$f_decorated = f = deco(f));
}
var $$f_accessor_export = $$f_accessor();
export { $$f_accessor_export as f }
console.log(g(1)); Also got undefined and throws as above. So there's still no way to keep the behavior consistent between decorated and undecorated functions. The result becomes: The decorated function would be hoisted in most cases, but not hoisted in some special cases That's a pretty bad idea for consistency. Also, making decorator affects execution of dependent files would break current module semantics, and making transformer depends on external files could be very complex (if possible) and cannot help with libraries.
Not possible even with that landed. |
How would it be if we closed this thread and continued discussion on @iddan 's new repo? |
@littledan Fine with me, thanks. :) |
@trotyl @rbuckton I copy the recent related comments to iddan/proposal-function-expression-decorators#3 , @littledan you can close or lock this thread, thank you. |
Hi everyone, try {
test();
}
catch (e) {
console.log(e.message); // "TypeError: decorator is not a function"
}
@decorator
function test() {
console.log('test');
}
var decorator = function() {
console.log('decorator');
}
test();
// decorator
// test This seems to me to be the expected behavior : the We have exactly the same probem with classes: var namespace = {};
@namespace.decorator
class Test {
}
new Test(); "TypeError: namespace.decorator is not a function"
var namespace = {
decorator : function() {}
} |
@AbrahamTewa As @hax said above, let's follow up in iddan/proposal-function-expression-decorators#3 . Closing this thread per @hax's suggestion to avoid confusion. |
Is there any update on function decorators? TypeScript is implementing decorators, so this may be the right time to propose something for function decorators. |
Function decorators would be a separate proposal and would need to advance through the stage process from the beginning on their own, so they do not have much of impact on TS implementing the current decorators proposal. |
Hi,
At the bottom of the README, one can read "Arguments and function declarations cannot be decorated.".
Is there a reason why functions should not be decorated?
Coming from a Python development background, my (only) use-case for decorators is to wrap functions, allowing me to extract redundant logic, short-circuit and do other great meta-programming things.
I would expect to be able to write code like:
and
Edit Maybe a better way of writing lambdas with decorators:
Argument decoration seems like a great feature as well:
Thank you!
The text was updated successfully, but these errors were encountered: