Open trusktr opened 1 year ago
It is very suggestive, but I have some comments:
Let's remember that a decorator is a function that is applied as a decorator, it is not a specific artifact. Such a syntax should be applied as a generic composition of functions, not as something specific to functions used as decorators. const func3 = func1 func2
.
A decorator can be evaluated in two steps, i.e., first a function is executed that returns the decorator function, as in @logger('warning')
. That syntax complicates this behavior a bit.
It is very easy to build a function that combines decorator functions into one. It is not necessary to define a specific syntax:
const Mixer = (...decorators) =>
(element, descriptor) =>
decorators.reverse().reduce((element, decorator) => decorator(element, descriptor) || element, element);
let cool2 = Mixer(cool, logged);
@falentio Yeah, my syntax idea was not very good. 😄
@pabloalmunia That Mixer
idea looks like it is on the right track, but here's a TS playground showing that the result of Mixer
is different than with plain decorators for class fields. Looks like class field decorators are supposed to always receive undefined
for their value parameter.
Here's a Babel repl showing the same issue with Mixer
(make a whitespace modification to make it run the code).
Even with some adjustments to Mixer
, it is definitely not as simple as @one @two @three
where decorator syntax has all the semantics baked right in.
About syntax, what if it were
const composed = @cool @logged('warning')
class MyClass {
@composed foo = 123
@composed bar = 123
}
where composed
is now a function
reference?
In the future when/if function decorators come out,
// this would not compose, but would decorate the function
const someFunc = @cool @logged('warning') function() {...}
// this would compose
const composed = @cool @logged('warning')
const otherFunc = @composed function() {...}
Would it perhaps just be a decorator composition expression? Maybe when decorators are written out without decorating something, they just create a composed function.
Example 1:
// this would compose
const composed = (level) => @cool @logged(level)
const otherFunc = @composed('warning') function() {...}
Example 2:
function foo(decorator) {
return @decorator function() {...}
}
foo(@cool @logged('warning'))
Example 3:
console.log(typeof (@one @two @three)) // "function"
A library wants to import decorators, and compose them into new ones to export the new ones. For example, for a custom element library, it might do this:
import {reactive} from 'some-reactive-library' // @reactive decorator makes a class field reactive
// _attribute decorator implementation
function _attribute() {
// ... map HTML attribute to JS field ...
}
/** A decorator that does two things: maps attributes to JS properties, and make properties reactive. */
export const attribute = @_attribute @reactive
End user:
import {createEffect} from 'some-reactive-library'
import {customElement, attribute} from 'some-custom-element-library'
@customElement('my-el')
class MyEl extends HTMLElement {
@attribute name = "Batman"
}
const el = new MyEl()
document.body.append(el)
createEffect(() => {
// This re-runs any time `name` changes because `@attribute` is composed with `@reactive`
console.log(el.name)
})
el.setAttribute('name', 'Superman') // triggers the effect, logs "Superman" to console
Right now, composing decorators is much too cumbersome. You have to write conditional branches inside of a new function to handle all the
kind
s of decorators, and often you repeat logic, and can get it wrong.Rather than show how to compose decorators by using multiple
function
s inside a newfunction
, which I'm sure you're all familiar with, let me show the desired feature:Currently:
Idea (totally random syntax choice, but to show the idea):
This achieves the same thing as composing both
cool
andlogged
function
s together inside a newcool2
function, but in a much simpler way for the author of the composed decorator.