You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
value::X:foo(...args) currently support both constructors and namespace objects by default.
For constructors, it works as X.prototype.foo.call(value, ...args). For namespace objects, it works as X.foo.call(X, value, ...args).
Such design is try to reuse the ecosystem of util-function style libraries like underscore/lodash, make them just work without any modification, but have some issues:
Not easy to explain and cause confusion.
Though using first arg as receiver matches the most real-world util-function usage, there are some cases should use ...args as receiver (eg. max(...args) -> args::max()), and there are rare cases (lodash/fp) use last arg as receiver, so still need manually customization.
Constructors could also have static methods, normally they are util functions (so the constructor also behave as namespace objects), theoretically there could be prototype methods and static methods with same name, which take precedence? Actually classical (not es module version) underscore/lodash already have such conflict (prototype methods for oop style or seq methods), to make it work as expect (most people only use util-function style), we need to make static methods take precedence, but it may not fit for other cases.
Finally, if the author customize the adaptation (via Symbol.extension hook) later, bring potential breaking change. This is especially bad for built-in/platform APIs because they do not have versioning.
To make module namespace work, import ::{foo} from "m" does not desugar to import {foo} from "m"; const ::foo = foo, but desugar to import * as m from "m"; const ::{foo} from m. This works but too subtle.
Some authors want full control of how their library could be used and it's annoying to get the bug report because of possible misuse of their libraries as extensions.
As the feedbacks and the analysis, adapting the util-function style to method style is good, but adapt them automatically is not good. Even without such auto adaption, we already have symbol-based customization, if people want to use exist libraries as extensions, they always can adapt them manually, so removing auto adaptation won't lose power too much. The problem is how to make the customization easy, and optimize for common cases.
Rough Intro of the new design
X can't be used as extension if X[Symbol.extension] is falsy, aka. value::X:foo always throw TypeError, const ::{foo} from X also throw.
Constructors still can be used as extensions (from prototype methods) by default, via built-in Function.prototype[Symbol.extension]. Authors could customize the behavior of their classes if they want, or even use C[Symbol.extension] = null to nullify it.
To use namespace object as extensions, you need to specify it explicitly: X[Symbol.extension] = {}. And each functions on X need to mark itself could be used as extension methods/accessors, or use X[Symbol.extension] = { config: ... } to provide these settings. (see below for details)
This proposal only specify Module Namespace objects have built-in [Symbol.extension] property to allow them be used as extension (from exported functions). Follow-on proposals and web APIs could add [Symbol.extension] property to other built-ins or platform APIs.
By default functions can't be used as extension methods directly. This avoid the misuse of non-method functions, and avoid the confusion of free methods which some delegates (include myself) worry about.
Previously, the proposal already rule out arrow functions, bound functions and constructors to avoid misuse, but can't avoid free methods totally.
const ::getX=functiongetX(o){returno.x}// previously allowed, now throw TypeError
In the new design, we unify the form of extension method and accessor to {invoke, get, set} descriptor:
To simplify the adaptation of functions to extension methods, we introduce a new well-known symbol Symbol.invokeStyle to allow authors define if/how a function could be invoked as an extension method.
functiongetX(o){returno.x}// if invoked as method, the receiver would be pass to getX as the first argumentgetX[Symbol.invokeStyle]={receiver: "first"}// other possible values are {receiver: "last"}, {receiver: "spread"}// {receiver: "first"} is the default value, so could just write// getX[Symbol.invokeStyle] = {}const ::getX=getXleto={x: 42}
o::getX()// 42
Could also specify it to be used as getter:
functiongetX(o){returno.x}getX[Symbol.invokeStyle]={kind: "get"}// receiver default to "first"const ::x=getXleto={x: 42}
o::x// 42
It would be simple if we have function decorators in the future:
@Extension.getfunctiongetX(o){returno.x}
Another exception will be functions with this parameter if this parameter proposal could advance, because this parameter already explicitly denote it's a method.
Note, if isEqual do not have [Symbol.invokeStyle] (or falsy), import ::{isEqual} and v::lodash:isEqual() would throw TypeError. This protect the users from misuse as early as possible.
To enable normal objects which is a collection of some util functions to be used as extensions like module namespace object:
With all these modification, we solve the related issues and still keep the goal of this proposal, allow current libraries in the ecosystem could be upgraded to support extensions easily, allow users import same API in the style as they need (extensions or util-functions style) without manually conversion. Note it's important to avoid two set of APIs of same features (lodash/fp is an example of such failure), which call-this proposal also noticed but can't solve well.
The text was updated successfully, but these errors were encountered:
Current design and the pitfalls
value::X:foo(...args)
currently support both constructors and namespace objects by default.For constructors, it works as
X.prototype.foo.call(value, ...args)
. For namespace objects, it works asX.foo.call(X, value, ...args)
.Such design is try to reuse the ecosystem of util-function style libraries like underscore/lodash, make them just work without any modification, but have some issues:
...args
as receiver (eg.max(...args)
->args::max()
), and there are rare cases (lodash/fp) use last arg as receiver, so still need manually customization.Symbol.extension
hook) later, bring potential breaking change. This is especially bad for built-in/platform APIs because they do not have versioning.import ::{foo} from "m"
does not desugar toimport {foo} from "m"; const ::foo = foo
, but desugar toimport * as m from "m"; const ::{foo} from m
. This works but too subtle.As the feedbacks and the analysis, adapting the util-function style to method style is good, but adapt them automatically is not good. Even without such auto adaption, we already have symbol-based customization, if people want to use exist libraries as extensions, they always can adapt them manually, so removing auto adaptation won't lose power too much. The problem is how to make the customization easy, and optimize for common cases.
Rough Intro of the new design
X
can't be used as extension ifX[Symbol.extension]
is falsy, aka.value::X:foo
always throwTypeError
,const ::{foo} from X
also throw.Constructors still can be used as extensions (from prototype methods) by default, via built-in
Function.prototype[Symbol.extension]
. Authors could customize the behavior of their classes if they want, or even useC[Symbol.extension] = null
to nullify it.To use namespace object as extensions, you need to specify it explicitly:
X[Symbol.extension] = {}
. And each functions onX
need to mark itself could be used as extension methods/accessors, or useX[Symbol.extension] = { config: ... }
to provide these settings. (see below for details)This proposal only specify Module Namespace objects have built-in
[Symbol.extension]
property to allow them be used as extension (from exported functions). Follow-on proposals and web APIs could add[Symbol.extension]
property to other built-ins or platform APIs.By default functions can't be used as extension methods directly. This avoid the misuse of non-method functions, and avoid the confusion of free methods which some delegates (include myself) worry about.
Previously, the proposal already rule out arrow functions, bound functions and constructors to avoid misuse, but can't avoid free methods totally.
In the new design, we unify the form of extension method and accessor to
{invoke, get, set}
descriptor:To simplify the adaptation of functions to extension methods, we introduce a new well-known symbol
Symbol.invokeStyle
to allow authors define if/how a function could be invoked as an extension method.Could also specify it to be used as getter:
It would be simple if we have function decorators in the future:
Another exception will be functions with
this
parameter ifthis
parameter proposal could advance, becausethis
parameter already explicitly denote it's a method.Consider
lodash.isEqual
as example:To use it as extension method, it's trivial to convert
isEqual
to a extension method:A much better solution is allowing the maintainers of lodash to define the adaptation:
Note, if
isEqual
do not have[Symbol.invokeStyle]
(or falsy),import ::{isEqual}
andv::lodash:isEqual()
would throw TypeError. This protect the users from misuse as early as possible.To enable normal objects which is a collection of some util functions to be used as extensions like module namespace object:
Add
[Symbol.invokeStyle]
to each util functions is wordy, so we allow[Symbol.extension]
to denote the mapping directly:Summary
With all these modification, we solve the related issues and still keep the goal of this proposal, allow current libraries in the ecosystem could be upgraded to support extensions easily, allow users import same API in the style as they need (extensions or util-functions style) without manually conversion. Note it's important to avoid two set of APIs of same features (lodash/fp is an example of such failure), which call-this proposal also noticed but can't solve well.
The text was updated successfully, but these errors were encountered: