Closed samholmes closed 2 years ago
I realize the implementation effort may be significant for transpilers due to the nature of how getter/setters function exclusively on properties of objects. In order for a decorator a variable, constant, or parameter, then perhaps each decorator kind should be "accessor"
. This way the underlying data structure on decorated variable, constants, parameters is a "top-level auto-accessor". In this sense, what I am proposing is a top-level accessor specification.
Having top-level accessors and allowing them to be decorated with decorators enables a very elegant syntax sugar which not only can we apply a runtime type system as mentioned above, but also functional reactive programming (FRP) patterns:
let @reactive() seconds = 23
let @reactive(num) minutes = parseInt(seconds / 60)
setInterval(() => {
seconds += 1
}, 1000)
function getTimeString() {
return `${minutes} minutes and ${seconds} seconds`
}
In this example, a runtime and a function reactive
is implemented such that it enables FRP. The reactive
function accepts zero or more accessor arguments (tracked accessors) and returns a decorator which is used to decorate an accessor with reactivity. The decorator returns a new accessor with properties:
get
retrieves a cached valueupdate
the original accessor's get
function (the assignment expression)set
changes the cached value and invokes any dependency accessor's update
method (the dependency graph is conceivably maintained and constructed by the runtime; details spared for brevity, but refer to ironjs.org for an example implementation).What this means to illustrate is not valid use-case that is enabled by extending the definition of the decorator proposal. Hopefully this strengthens the argument for such an extension.
It is conceivable that transpilers could implement this syntactic and semantic change to JS by transpiling any occurrence of an identifier which has been semantically changed from a "regular variable/constant" to an "accessor variable/constant" by replacement:
let @decorator foo = 23
console.log(foo)
foo = 42
// becomes
var foo = _decorator(_initAccessor(() => 23 ).value)
console.log(foo.value)
foo.value = 42
In short, the identifier is replaced with an object containing a single accessor called value
. I may be erroneous in my illustrative example here, but it's a rough idea on how this could be implemented for a transpiler.
what you have proposed has already been in the EXTENSION.md of this repo.
https://github.com/tc39/proposal-decorators/blob/master/EXTENSIONS.md#let-decorators
I wasn't aware of these extensions. Good to know.
Looks like it covers most of what I mentioned in this issue. However, one other thing it doesn't cover and I haven't quite mentioned, is the idea to decorate the only return value of a function:
function name() @decorator {
return value
}
const name = () @decorator => value
Unless I missed it, this would be worthwhile to add to consideration in the extensions. Having this could be useful to be able to decorate just the return value. Otherwise, the only way to decorate the return value would be to decorate the entire function declaration or assignment value for an identifier and this would require a transformation of decorator kinds:
const decoratorForAccessors = ...
// Will work
const @decoratorForAccessors foo = value
// WIll not work
@decoratorForAccessors
function foo() {
// ...
}
// Will work if you wrap with some transformer
@applyToReturn(decoratorForAccessors)
function foo() {
// ...
}
I'm a big fan of decorater, But in mainstream Frontend frameworks, Class style code are not natively supported. Like React is functional, Vue is based on plain object. No handy way to use decorater if decorater just support Class.
Decorater definitely need support universal definition
Yeah, decorators on other parts will be super handy in an add-on proposal later.
I want to throw out there that making reactive variables with let @decorator
and using them in reactive functions with @effect function
, or similar, may just be wonderful:
Example:
// count.js
import {signal} from 'really-awesome-lib'
// reactive variable (signal)
@readonly
let @signal count = 0
// count is readonly outside, but still writable within the module.
setInterval(() => count++, 1000)
@readonly
modified the export binding that is used outside of the module, while @signal
modified the variable accessor that is used within the module
// main.js
import {createEffect} from 'really-awesome-lib'
import {count} from './count.js'
// This automatically re-runs any time `count` changes (dependency-tracking reactivity).
// The reactive implementation tracks any signals accessed within the function (dependencies)
// in order to re-run the function when the signals change.
createEffect(() => {
console.log('count:', count)
})
// later
count += 2 // readonly throws an error, count is not writable outside the module where it came from.
Closing as a duplicate of #306
Currently the decorators proposal specifies decorators which can be applied to classes and class methods/properties. I would like to explore an extension to this proposal whereby one could apply a function to any definition (variable, constant, parameter) using the decorator syntax:
Motivation
The motivation to having this extension to the decorator proposal would be cases where you would like to run a function to validate the value assigned. This is particularly useful for runtime type schemes or data normalization.
These examples are not just contrived, but rather are very practical. Allow me to make a case.
Imagine a user-land runtime type system where each type was simply a function which validated input according to that type or threw an
TypeError
. Such a runtime type system could leverage decorator syntax to annotate the implementation with types and improve the type-safety of a codebase. Furthermore, user-land optimization tooling may be built which may do static analysis of the source code to determine where these runtime types may be safely removed from the transpiled output. This sort of source code not only renders itself as more safe, but more strict by nature; making JavaScript more correct by means of a declarative API (using decorators).Conclusion
The use-cases that come from allowing decorators to be used in more definition syntaxes are useful. We have an opportunity to add a lot of value to the developer by this extension. However, more discussion is needed to identify shortcomings and potential problematic cases.