tc39 / proposal-decorators

Decorators for ES6 classes
https://arai-a.github.io/ecma262-compare/?pr=2417
2.76k stars 105 forks source link

[Feature Request] allow decorate for ALL javascript entities #306

Open MaxmaxmaximusGitHub opened 4 years ago

MaxmaxmaximusGitHub commented 4 years ago

ReactJS usage example:

React functional components:

import {memo, forwardRef} from 'react'

@memo
@forwardRef
export default function Button() {
  return <button>my button</button>
}

React HOC:

import {withRouter} from 'router'

@withRouter
export default function Header({ router }) {
  return <header>my header {router}</button>
}

decorators must be able wrap value and return new value, if they wish it. as well as decorators should be applied to any values, including variables. and this should be reflected in the Descriptor kind

we should be able to write like this:

var q = 200
var w = 300
var e = q + w 

@int var q = 200
@int var w = 300
@int var e = q + w

however, in the case of variables, I'm not sure, since this is probably a big blow to performance, because we will have to use the decorator every time we try to read or write the variable. but nevertheless, it’s definitely worth decorating the functions and function args.

and we can create watchable variables, like getters and setters

function watch(descriptor) {
   //  descriptor.king == "variable"
  return {get(){ }, set(){ }}
}

@watch
var q = 200

q = 300 // calls setter
q // calls getter

or like this

function plus(@int a, @int b ) { a + b }

and the decorator will round the number

or like this:

function limitCount(descriptor){
  return function decorator(count){ 
    if(count > 100) throw new Error
  }
}

function getMessages( @limitCount count ){ }

or like this:

function onWebsocketRequest (@clear params){
   console.log(params)
}

or like this:

@mustAuth
@mustBeInGroup('admin')
function getUsersList ( ) {

}

or like this:

jQuery.prototype.css = @normalizeArgs function (cssProps){

}

jQuery.css('prop', val) // all arguments are normalized
jQuery.css({'prop', val}) // all arguments are normalized

a many of examples for using decorators, and not just for decorating classes.

NullVoxPopuli commented 4 years ago

This'd be functionally the same as existing class decorators, yeah?

MaxmaxmaximusGitHub commented 4 years ago

@NullVoxPopuli yes, decorators should be able to decorate anything by analyzing Descriptor, and should be able to return a new value by wrapping the original, as is customary in decorators in functional programming languages. In this way, higher-order functions will be created using decorators.

pzuraq commented 4 years ago

I believe the previous discussion around this was that it was out of scope for this proposal, because they might have decently different semantics depending on what the proposal ends up looking like, and it would be too much to do at once. The idea was to followup with additional proposals that add things like function decorators, parameter decorators, etc.

It's worth noting that the "before/after export" debate would probably have an impact here though as well. I think for instance, with the "after" syntax, the example you have at the end with jquery would end up being:

jQuery.prototype.css = @normalizeArgs function (cssProps){

}
littledan commented 4 years ago

I agree with @pzuraq here. We're going to have to limit the scope of this proposal, initially, in order to be able to make progress and make a standard. I would like to be able to decorate all JavaScript entities! But this proposal focuses on classes, where there is a clearly demonstrated demand. Function decorators face unresolved conflicts for semantics around hoisting.

MaxmaxmaximusGitHub commented 4 years ago

Function decorators face unresolved conflicts for semantics around hoisting.

everything is simple, if a decorator is applied to the function declaration, it becomesfunction expression =) since the use of the decorator is not a declaration, is action (expression)

functions

@decorator(2 + 2) // decorator can use expression 2 + 2, cuz decorator is expression
function lol(){ }

is:

var lol = decorator({kind: 'function', options: 2 + 2})( function lol(){} )

args

function lol (@int age){  

}

is:

function lol(age){
  age = int({kind: 'argument'})( age )
}
littledan commented 4 years ago

Those semantics come into conflict with some expectations that have been articulated:

I don't think it's impossible to overcome these issues, but it involves complicated tradeoffs. For that reason, this proposal is scoped to decorators on classes and elements of classes.

MaxmaxmaximusGitHub commented 4 years ago

@littledan

Old code does not use decorators, this will cause a syntax error.

The new code, in which decorators are used, expresses an explicit intention, and the programmer realizes that the function does not rise. When using decorators, Function Declaration becomes Function Expression.

Thus, backward compatibility does not break.

trusktr commented 4 years ago

AssemblyScript is TypeScript that compiles to WebAssembly, but it has some incompatible additional features like decorators for functions (I haven't checked how hoisting works, also don't use AS function decorators if you intend to also compile to JavaScript with TypeScript compiler).

It'd would be neat to support this.

ceigey commented 4 years ago

(acknowledging this is discussion for a followup proposal and out if scope for this one)

If you could decorate variable declarations, then you could decorate functions indirectly, without affecting hoisting (I think).

E.g.

@myDecorator
let myFun = (x, y) => {
  // ....
}

(I avoided const for simplicity; much easier to reassign to myFun than figure out name mangling and other things I guess)

I really like the idea of argument decorators, that opens up new opportunities for design by contract and static type checking.

@returns(z => Number.isFinite(z))
let myFunc = (@isNum x, @isNum y) => {
  return x + y
}
littledan commented 4 years ago

I think decorating more kinds of syntactic elements is an exciting idea, and I really do support this investigation. I've put a writeup in https://github.com/tc39/proposal-decorators/blob/master/EXTENSIONS.md. I'd be happy to get PRs to add more ideas.

MaxmaxmaximusAWS commented 3 years ago
// this is decorator of function (kind === "function")
Object.prop = @decorator function () {}

// this is decorator of object accessor (kind === "accessor") 
// but we can also decorate the assigned value of the function here, right during the assignment.
@decorator
Object.prop = function () {}

Moreover, we can write universal decorators that behave differently depending on kind. =)

MaxGraey commented 2 years ago

I'd like to bring it up again. Especially interesting are top level function decorators and block level decorators. The main motivation for top-level functions is the stack-based composition.

For example, these two examples not only look different, but also have different semantics, different hoisting, different readability:

const add = type.lhs("a", "int")(type.rhs("b", "int")(function (a, b) {
  return a + b;
});

and

@type.lhs("a", "int")
@type.rhs("b", "int")
function add(a, b) {
   return a + b;
}
MaxmaxmaximusAWS commented 2 years ago

I'd like to bring it up again. Especially interesting are top level function decorators and block level decorators. The main motivation for top-level functions is the stack-based composition.

For example, these two examples not only look different, but also have different semantics, different hoisting, different readability:

const add = type.lhs("a", "int")(type.rhs("b", "int")(function (a, b) {
  return a + b;
});

and

@type.lhs("a", "int")
@type.rhs("b", "int")
function add(a, b) {
   return a + b;
}

what is the question? executing decorators is an "expression" and it's "executing", you mean that should the subsequent decorator get the bare result, or should it somehow be able to sense that it's getting the result of another decorator, and have access to meta information? What's the question? what is the dilemma =)?

MaxGraey commented 2 years ago

It's not a question, it's more of an emphasis of the problem. In this case, introducing top-level function decorators such as @foo function boo() {} will cause such a function to become expression based and will break hoisting. I'm interested in how this could be solved and preserve hoisting ability

MaxmaxmaximusAWS commented 2 years ago

@MaxGraey no way. applying a decorator is an action. which means it happens over the essence. in this case over a function. the very mechanism of declaring functions is a vestige and birth trauma from the creator of javascript. we should not be sensitive about the ability of functions to rise. We, for the sake of good, should neglect this feature. Therefore, applying a decorator is an ACTION, imperative, line by line. And what is under the influence of the decorator is not subject to the function bubbling mechanics. I think so. There is no point in trying to keep the harmful lifting mechanic.