-
Notifications
You must be signed in to change notification settings - Fork 16
Decorate a function #35
Comments
What's the implementation of your decorators? |
a function which takes a component and return a (maybe another new) component |
The usual problem with decorating functions is hoisting. E.g. would the code below throw? Would it use the state of It's somewhat simpler for function expressions - but then the syntax offers little over the existing solution of just calling the "decorator" function. MyTarget();
let dec = decoratorA;
MyTarget();
dec = decoratorB;
MyTarget();
@dec
function MyTarget() {}
MyTarget(); |
@jkrems IMHO, decorating function expressions is far cleaner than the wrapper syntax: const MyComponent = compose(
hoc1,
hoc2,
hoc3
)(function () {
// ...
})
@hoc1
@hoc2
@hoc3
const MyComponent = function () {
} |
@littledan What do you think about this issue? It has come up over and over again; the inability to decorate a function is completely unintuitive and seems like a showstopper to me. |
We were at an impasse before due to the ugly question of how decorators and function hoisting would interact. If you have a decorator and a function defined in the same block/top-level, it's not clear how to combine function hoisting with applying the decorators. I'm not sure what the solution to this problem is. I see how function decorators are useful, but what I don't see is how it's a showstopper--the use cases for function decorators and class decorators are pretty distinct, and the features could proceed separately, right? |
The hoisting problem disappears if the decorators are applied to the assignment (with or without variable declaration), but it could indeed be another proposal. |
@julien-f You mean we'd allow decorators on assignments of function literals, but not on function declarations? Interesting idea. I am not sure if everyone would buy into that. |
I’m curious re:
since it doesn’t seem to be explained yet in this thread. Class and method decorators provide hooks into facets of class declaration that otherwise require somewhat verbose, imperative mutations of the constructor and prototype after declaration. In languages that have function decorators it is usually (I think?) because there are similar things to hook into (or because functions aren’t first class). It’s not obvious to me though, looking at this, what decorating a function actually means except "a new syntax for calling" that happens to accept only one kind of first argument. Is there something meaningful that this would provide? |
This seems to have a fairly simply solutions. Bind decorated function declarations exactly the same way that class declarations are bound. They are in a TDZ until evaluation reach the declaration at which point the decorator(s) are applied (however you want to define their semantics) to produce the function value that initialized the bound function name. |
As the OP illustrates, some libraries like React allow the user to gracefully refactor from a function to a class which implements a stateful version of that function (or vice versa). Simplified: interface RenderFunction {
(props) : Element;
}
interface Component {
props: object;
render(): Element;
}
type Renderable = RenderFunction | Component; If a decorator is useful for export const A = connect(...)(
function render(props) {
// ...
}
);
export const B = connect(...)(
class extends Component {
render() {
// ...
}
}
); With the current proposal, we could use the pretty decorator syntax for export const A = connect(...)(
function render(props) {
// ...
}
);
@connect(...)
export class B {
render() {
// ...
}
} This discrepancy is both unintuitive and undesirable. If for some reason we are unable to have function decorators, we'll be stuck in this unfortunate situation forever. Therefore I think that we need an answer to the function decorator problem in order to proceed with confidence. |
@zenparsing That example assumes stage 0 decorators where the decorator is just |
@jkrems I understand, and that's why I used quotes when describing |
@allenwb We discussed this option before. The obvious downside is the refactoring hazard--it may be surprising to lose function hoisting, among other observable changes, when you just add a decorator. Other alternatives do seem worse, though. Is this refactoring hazard worth it to everyone? |
It is for me. Currently the community is kind of on-hold with decorators as the babel team omits it from their presets and create-react-app excludes this feature as well. I think as this is only a proposal in stage-2 it has the mandate to change. |
I think the refactoring hazard fear is overblown. If you accidentally decorate a function that needs to be hoisted, wouldn't you see the error immediately? For example: import { wrap } from 'some-lib'
var g = f() + 20
// @wrap <-- This will fail fast
function f () { return 10 } Uncommenting the In many cases (or even most) it won't even matter, such as this slightly different example: import { wrap } from 'some-lib'
export var g = (x) => f() + x
// @wrap <-- Adding this is no problem
function f () { return 10 } |
@gilbert I guess it shouldn't be so hard to debug because of the quick ReferenceErrors, but this isn't the same as lending itself to an easy-to-understand mental model long-term. Function hoisting is already a bit complicated (e.g., switching to 'let'-style in a block, Annex B 3.3 in sloppy mode); this adds more complexity if you're scanning code and trying to figure out what it's doing. |
But it might be worthwhile. How do they handle it in Python? Are they facing the same problems? |
I have suggested a so-called composition operator which may take this issue away from proposal-decorators. How would you think about it? |
@iddan Python doesn't do function hoisting. You can define functions top-down rather than bottom-up only as long as you don't actually use them before everything is defined. In Python, instead, the semantics are to define the functions one at a time as the code is executing. These are the semantics proposed by @allenwb above. They would "work", the only problem is that it's a big break from the way JavaScript works generally. For example, in JS, you can generally do something like this: f();
function f() { } In Python, or in JS |
f(); // f is not a function
const f = () => {} 1.1. If we are focusing the discussion to @g const f = () => {}: is it important to support decorating non-functional variables? @g
const f = () => {} // got you!
@g
const a = {} // what?
f() // <- yes
function f() {}
g() // <- no
@h
function g() {} |
Any comments? |
I'm not totally satisfied by your solution as many programmers like to use the function declaration syntax, and it seems incongruous to move away from it. I also don't understand why, if you're already using I'm thinking that we should handle function decorators as a follow-on to class decorators, not in the first pass. I have another idea as to a solution to the hoisting problem which doesn't depend on changing the declaration form. The idea is: decorators which are defined in the same block scope can't themselves be decorated--only the set of undecorated function declarations are available in scope when evaluating the decorator expression in the function declaration, and others are defined in an inner nested scope. In practice, this means that a very unlikely case will yield a ReferenceError, which can be avoided by moving the decorator to a separate module (either CJS or ESM would work). What do you think? |
Can you give a code example of the edge case? |
Another solution here would be to not allow decorators on classes. If we only allowed decorators on class elements (and later, property definitions in object literals) we get a couple of benefits:
|
I don't really understand why saying no to one feature request will make another feature request go away... |
Because it clearly defines the scope of the feature. One of the main risks that I see in introducing decorators into the language (and, to be clear, I think the risks of the current proposal outweigh the benefits) is that, as a metaprogramming feature blessed with premium syntactic space, they are subject to scope creep. With the current design, scope creep is almost guaranteed. Various parties want to decorate functions, parameters, variable declarations, etc. all for ostensibly valid use cases. In my opinion, this will not help the language long-term, especially a language that is so flexible already. We already have transpilers; there's no need to open up this pandora's box. |
Function decorators could definitely be useful. This proposal leaves them out, but they could be good to pursue as a follow on. Do you see anything here that would block that path? |
Let's continue the discussion at tc39/proposal-decorators#40 . |
Here is my story:
Firstly, I wrote React components using HOCs pattern. Say, I have some hocs and compose them into a component like this:
Then, I learned the decorator syntax and changed my code into such format:
But this is not perfect. The final great format should be like this:
But currently decorator can not be applied to function, so sad :(
The text was updated successfully, but these errors were encountered: