unlike any other class, Function can't be subclassed due CSP shenanigans
any extension of Function lands inevitably in the global prototype ... hopefully there are not many needed extensions of it out there, if not for partial applications ... here the proposal is simple and crystal clear: add new to its prototype before the community does it in subtly different ways to do the same thing
if the Function is considered untouchable, then it's safe to extend from the community
if the Function is not considered untouchable, this is the easiest to reason about feature request from projects already suffering the lack of such method (see presented real-world cases)
when it comes to Proxy and/or foreign programming languages, nobody owns anything but performance can't be penalized for most common operations (classes instantiation)
there's no will to consider this proposal, there's no will to give an OK to provide a community solution ... this is a political limbo I personally don't like but also don't care much (I will provide a library to extend Function.prototype with new and use/promote that as I don't like being stuck in life ... in general)
there is no single example of a braking library if this proposal goes through and the only arguments use examples that won't be affected ... can somebody please provide a valid reason not to move this forward so that I can explain why we can't have Function.prototype.new and it'd be bad for me to extend it?
Python/Rust/Kotlin etc can pretend all JS functions have a .new method even if it's not there in reality. If the object says it HasProperty("new") it will call that method like normal, other wise Construct the target instead.
For the record, this is pretty much the approach we take with Pyodide.
I'm still entirely unclear on why Function.prototype.new and F.new() is even the slightest bit different from new F() as a transpiler target - the only difference to JS devs is ergonomics, but that concern doesn't apply at all to non-JS source languages.
none of the source language can transpile (or should) on the fly foreign JS syntax ... you write Python in a file and you have errors out of IDE out of the box if there is new Thing in there.
This change, as mentioned a few times already, won't affect JS developers, it will enhance interoperability with other PLs ... as easy as that.
edit no foreign PL wants JS syntax in it. This dooms the foreign PL in case new is a reserved word or the PL wants to add new in the future. Assuming the rest of the world needs or use tools to solve everything like "we" do in the JS world really represents how bad and normalized is the current state of JS, imho (as in: "just transpile the code" or "just use tools to solve this").
I don't understand what you mean but you seem to not understand how these PLs are used on the Web.
You have a file.py file which gotta be valid Python and it imports bindings from te JS world and it cannot use new Thing in there ... do you understand transpilers are neither desired nor an option in this scenario?
from js import Date
# invalid Python
# new Date()
# home-made solution
Date.new()
These PLs use runtime evaluation too and these can evaluate code on the page as well, if it's <script type="python"> ... these can't and shouldn't transpile anything and Python transpiled in JS makes no sense.
Accordingly, I don't think transpilers are an answer (or even an option) to this proposal, unless I've misunderstood your comment.
And as I said in my earlier comment, what is wrong with them having Date.new()?
If JS added a Function.prototype.new, then Date.new() would exist in these "write JS in another language" tools (but authors could override it to do something unrelated, so it's not necessarily dependable as a construction target). If the "write JS in another language" tool interprets Date.new() as a construction, then that works just as well (and authors can still have classes with a new() method, so there's still the potential for a clash).
It doesn't matter whether JS adds .new() or not; the situation for these tools is the same. So they're not a justification for adding anything to JS, except possibly as a coordination point (everyone would do it the same way), but that's already mostly the case with the only real divergence being the choice between .new() and .create(). Is this lack of coordinated naming between tools a point of contention in the wild, such that it's worthwhile to adjust JS to guide them?
Otherwise you have to justify this in terms of benefit to JS itself, and in that case I think it's just a minor ergonomic benefit (tho with the downside, again, that as a method it's overrideable).
nothing wrong indeed, and having it backed by JS would make it absolutely right:
there is a standard to point at whenever any new PL would like to provide JS bindings
there is code for new-comers from other PLs that can use the same construct and be done with it (JS bindings interoperability that works as copy/paste across multiple runtimes could be a thing!)
there is the opportunity to Class.new.bind(Class) to provide dual syntax (one for new Bound, one for array.map(Bound)
there is a high chance both wasmoon (.create) and PythonMonkey (pm.new(pm.Class)(...args)) will align, making interoperability across interpreters easier to understand, experiment, and develop with
this is already the case in JS if you return somethig unrelated to a Proxy.construct trap but, having a common default behavior standardized would help aligning everyone around new meaning
there are no clashes, as mentioned already ... if a class wants to use a different new as static method, it's as welcome to do so as extending Array and provide a different slice could ... that's not clashing, that's OOP's ABC
that's the whole point of this proposal: provide that coordination point for something so simple to reason about it's hard to believe it took so many comments in here
yes, as you can see already 3 different ways in 4 interpreters ... and more are coming soon (anything that can target WASM will have its own implementation of new doing something slightly different from what's expected too).
JS is "the assembly of the Web" ... more like asking WASM to be slightly friendlier for everyone else targeting WASM, even if it's not supposed to be written manually, WASM ergonomics are under everyone eyes ... why should JS push-back on things as simple as this proposal? If this can't be done, nothing else to improve JS <-> WASM interoperability should, right?
That's OK ... heck that's desired in some case, and that's again OOP at its best and it would just work without issues.
Everyone on the thread, especially Tab, has already given you valid reasons not to move forward. I don't think you've managed to convince anyone in this thread at all, so far, that this is worth pursuing.
Specifically, PythonMonkey should just synthesize its own .new() method on objects, just as Pyodide already does. You seem to think that there's something wrong with that, and I've read and reread your responses here and I'm struggling to understand your English; to the extent that I can understand your arguments, they're just not correct.
class Class {
constructor() { this.x = 'foo'; }
static new() { return ({x: 'bar'}) }
};
These examples are both valid JS code that you can write today. Today, PythonMonkey allows you to write pythonmonkey.new(Class)().x, which is 'foo', or Class.new().x, which is bar().
I believe that you wrongly think that standardizing Function.prototype.new would solve this problem, but it wouldn't. Even if we standardize Function.prototype.new, there will still be two different things to call, new Class() and Class.new(), and they'd have different behaviors, returning 'foo' or 'bar' depending on which one you called. In no case would it be safe to completely remove pythonmonkey.new(); it would still be needed, at least in this case.
But that prollyfill would have no effect on these examples. Even if you run the prollyfill, the constructor would say 'foo' and the static method would say 'bar'.
But this is also why it would be OK for PythonMonkey to synthesize a .new() method, as Pyodide does.
You say they "can't," but they can. I know they can, because Pyodide already does.
And, worst of all, standardizing Function.prototype.new wouldn't even solve the ambiguity, or avoid a branch. Someone always has to check to see if the function has overridden its prototype, whether that's done on the JS side or on the side of PythonMonkey/Pyodide.
I'd like to rise an extra point of view ... you are a PL user that doesn't know JS but you know that's the DOM language too.
Now you write code in your PL that provides JS bindings and ... you can't learn JS from it ... or better, this user would start believing that in JS you need to Class.create([1, 2, 3]) while another learns that Class.new(1, 2, 3) is a thing while another learns that Class(1, 2, 3) should work too, because the Proxy trap is magic enough to understand that's a class and when invoked a new instance should be returned.
This will damage or ruin the learning curve for new comers and it's the smallest fix, easiest thing, to do from a JS point of view, to at least encourage all future bindings to do the right thing.
plot-twist these PLs start polluting the global JS environment Function.prototype to accomodate their need of having new like function that works for their intent ... that would doom new in JS for everyone else instead of playing nicely already, and being standard, for all the current and future PLs that would like to use the standard way of creating instances, when new Class is not an option for their PL.
It's a hard sell for JS consumers, but IMHO the easiest and most reasonable sell for the future of the Web to me ... but I guess "we do we" here, so I went ahead, foreseeing zero outcome, and published the module:
This is a polyfill for this hopefully landing proposal that, if won't land, hopefully will have enough adoption to convince foreign PLs that is the way.
they can't because there's no native JS way to understand if the typeof "function" is a class that can be constructed or not ... they are guessing they can but they can easily have issues or unexpected results there ... because there's no native way to disambiguate a class from a regular function in JS, that's legacy we carry, sometimes legacy desired too (see jQuery).
Apologies for not being mother tongue here, but it looks like you got what I am after, or what I am saying, and you telling me "I am not correct" is not correct to me neither ... tell me how to disambiguate a "real" class from a function in JS, then maybe you'll convince me I am not correct there when I say it's not possible to be sure a function is constructable.
Maybe JS misses a Symbol.constructable which would solve partially this debate but it would still not define how PLs should address the new Class case in their language, but that'd be a complete different thread or story.
edit ... actually, because they can't, if we had disambiguation for classes, in Python one would just write Thing() instead of needing Thing.new() at all, because Thing is known to be a class, not a generic function. The Thing.new() is a workaround that makes as much sense as this proposal does ... and it bugs me everyone is OK with any PL bastardizing as they want new Thing() in their PL because JS doesn't offer any way to do better ... this proposal was to offer that better way. Yes, new Class or Class.new can do different things, but that's true with Proxy all over the place already, so it's not a strong argument to me.
I am willing to re-explain, correct, edit, as much as needed, if that's necessary, but the message "good English or NO is the answer" is not really cool.
The person also doesn't realize/understand I work with Hood and filed or suggested already various Pyodide changes ... I actually deliver Pyodide in PyScript so that I haven't made up anything in here, just proposed a possible improvement for our daily use cases out of students or Python developers, among R, Ruby, Lua, or others, that need to understand why .new is needed at all in JS bindings and just Class() can't work.
If there was a standardized .new in the language, it'd be easier to explain it, beside what anyone does behind the scene ... right now it's burden on PLs documentation and APIs and nobody can tell them "you could do it better for end-users' sake" ... plus none of the learner would understand much of this discussion neither.
<!DOCTYPE html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
<script type="module">
let pyodide = await loadPyodide();
pyodide.runPython(`
import pyodide.code
Class = pyodide.code.run_js("(function Class() {this.x = 'foo'})")
y = Class.new() # look! on this line, I can call the synthesized Class.new
print(y.x) # prints foo
`);
</script>
</head>
</html>
This is what I was claiming Pyodide does. It's clearly false to say that Pyodide can't do this, because this code runs. And look at that Class.new() line! It works.
You were saying:
they can't because there's no native JS way to understand if the typeof "function" is a class that can be constructed or not ... they are guessing they can but they can easily have issues or unexpected results there
Pyodide sure is guessing. For example, here's that ambiguity issue you were worried about:
import pyodide.code
Class = pyodide.code.run_js("""
Class = (function Class() {this.x = 'foo'}),
Class['new'] = () => ({x: 'bar'}),
Class
""")
y = Class.new() # This calls the JsProxy's constructor
z = Class.as_object_map()["new"]() # This calls Class['new']
print(y.x) # prints foo
print(z.x) # prints bar
I think Pyodide's approach works great. I think PythonMonkey should do the same thing as Pyodide, which is clearly something that is possible. (Please stop saying that it isn't possible!) It makes the normal, default thing easy, but leaves the weird, exotic thing possible.
But the most important point is that even if Function.prototype.new were standardized, Pyodide would still need to guess what to do, and the ambiguity would remain. Behold, here's a sample that adds in your prollyfill:
<!DOCTYPE html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
<script src="https://unpkg.com/@ungap/new"></script>
<script type="module">
let pyodide = await loadPyodide();
pyodide.runPython(`
import pyodide.code
Class = pyodide.code.run_js("""
Class = (function Class() {this.x = 'foo'}),
Class['new'] = () => ({x: 'bar'}),
Class
""")
y = Class.new() # This calls the JsProxy's constructor
print(y.x) # prints foo
`);
</script>
</head>
Even with your prollyfill, Pyodide still has to guess whether to construct with new Class() or whether to call Class['new'](), because anyone could just clobber the Function.prototype.new implementation.
(In fact, if you add a line to Class.as_object_map()["new"]() after loading your prollyfill, Pyodide throws. So, here, we see a working counterexample, showing that adding to Function.prototype.new would break backwards compatibility with the exact kind of code that it's designed to serve. So, if you're looking for a technical reason not to ship this proposal, here it is: Your prollyfill breaks Pyodide 0.23.4.)
Indeed, if I were a developer working on Pyodide (which you are, I guess?) then even if Function.prototype.new were to land, I wouldn't use it; I'd just continue to call new Class() when someone calls Class.new() from Python, and allow users to futz around with Class['new'] via an ugly workaround if users insist on invoking that for some reason.
tell me how to disambiguate a "real" class from a function in JS,
There is no way. But Function.prototype.new doesn't provide a way to do that, either. The optimal thing to do when a Python user calls Class.new() on a JS function, is to just try to new Class() in JS and see if it throws.
"good English or NO is the answer " is not really cool.
That's not what I said. I mentioned your English because it's possible I was misunderstanding you. But I'm pretty sure I'm not… you're just saying that Pyodide "can't" do what it actually does. That's just wrong.
because try/catching every single apply trap both degrade performance and make intent less obvious ... as example, a new function(){} would return an instance of itself instead of being an utility that returned undefined at the end so I am not sure why you think that's even an option ... so having Thing() handled as new Thing() in the trap is by no mean a practice anyone should use.
We can't detect capitalized names neither, assuming that's a reasonable convention, because minifiers can happen for foreign libraries exposed as 3rd party in main.
I mean ... you understand I work with this stuff daily and yet you think you can explain to me how it works ... but thanks for the example though, let's see why I insist pyodide can't
Pyodide is inevitably guessing that a proxied function that access a new field is meant to construct an instance but above example shows that guessing leads to errors otherwise not present in the JS world.
Not at all. If the new was standardized at the Function.prototype level, pyodide wouldn't need to guess anything, it would just use the Proxy traps as they are.
# get trap on global context
js.Arrow
# get trap on Arrow function
js.Arrow.new
# apply trap on Arrow.new with
# Arrow as context + optional arguments
js.Arrow.new()
If new there is own property, everything would work as expected.
If new there is inherited from Function, it will throw like it would in JS because arrow can't be constructed.
If new there is inherited and Arrow is actually a class, it would construct the resulting object passing through the construct Proxy trap, which already holds the Arrow class.
Right now, because nobody is understanding what's the benefit of this proposal or is even needed, all interpreters need to guess or find workarounds, or use try/catch, or ... while ifnew was standard:
nobody would likely override it unless really desired
every interpreter would stop guessing around and just use Proxy traps without re-inventing the wheel
no ambiguity would exist unless meant to be ... that's the case of explicit Class['new'] = ...
It's demonstrated in this post they can't guess anything like that ... they can't pretend anything: we need a standard to rely on and less guessing to avoid issues like the one showed in here.
Back to the topic: it would still be possible from the Python side of affair to explicitly constructs via Reflect.construct(JSClass, [...args]) but that would be either for internal use cases or for the 0.1% of real-world scenarios out there where nobody is adding static new out of the blue and, when they do, it's because that's needed.
I hope now it's clear what pyodide or any other interpreter can do, and what they can't due lack of Symbol.constructable ... and again, if that symbol was backed into JS, none of this conversation would have ever happened because at that point the apply proxy trap would've had simply brand-checked classes (as in ES6+ classes) VS any other non-class function JS can offer.
If this proposal goes through, and until standardization, pyodide and all others have plenty of time to adjust their logic accordingly which, in the long term, would make pyodide and others more robust, not less, and it will guide any future player to do the right thing and use what's standard behavior in JS too.
That being said, my poly is not needed in Pyodide or MicroPython (WASM) but for what I could test including the poly ASAP as polyscript bundle, everything still works as expected, no regression in there.
Maybe an example about how this proposal makes everything better for everyone would work ...
How it is now
<!DOCTYPE html>
<head>
<script>
const traps = {
construct(target, args) {
console.log('construct');
return Reflect.construct(target, args);
},
get(target, name) {
console.log('get', name);
if (
// guessing + pretending
// still error prone
name === 'new' &&
typeof target === 'function' &&
!('new' in target)
) {
return (...args) => traps.construct(target, args);
}
// fallback
return target[name];
}
};
// example code: foreign class + pyodide proxy
class Thing {}
const $Thing = new Proxy(Thing, traps);
console.log($Thing.new());
</script>
</head>
</html>
How it could be instead
<!DOCTYPE html>
<head>
<script src="https://unpkg.com/@ungap/new"></script>
<script>
// this is literally it:no magic guessing or pretending
const traps = {
construct(target, args) {
console.log('construct');
return Reflect.construct(target, args);
},
get(target, name) {
console.log('get', name);
return target[name];
}
};
// example code: foreign class + pyodide proxy
class Thing {}
const $Thing = new Proxy(Thing, traps);
console.log($Thing.new());
</script>
</head>
</html>
Moving parts, possible shenanigans, guessing ... all gone: it just works out of Proxy primitives which makes it easier to maintain and reason about.
In a real-world scenario though, the get trap is way more complicated because of namespaces ... so let's see how a complete trap could look like (considering previous complexity on top without Function.prototype.new):
<!DOCTYPE html>
<head>
<script src="https://unpkg.com/@ungap/new"></script>
<script>
function get(target, name) {
console.log('get', name);
const value = target[name];
switch (typeof value) {
case 'function': return new Proxy(value, cbTraps);
case 'object': if (value) return new Proxy(value, objTraps);
default: return value;
}
}
const cbTraps = {
construct(target, args, proxy) {
console.log('construct', proxy);
return Reflect.construct(target, args);
},
apply(target, self, args) {
console.log('apply', self /* is proxy */);
return target.apply(self, args);
},
get
};
const objTraps = { get };
// example code w/ namespace
class Thing {}
const { some } = new Proxy({ some: { Thing }}, objTraps);
console.log(some.Thing.new());
</script>
</head>
</html>
Nowhere in my examples I need to branch out regular logic or consider special meaning for any property or do anything different from what I'd do regularly with JS ... and this is the reason I am proposing Function.prototype.new unless anyone can show me code that doesn't guess, never fails and is also easier or less complicated than just this one.
I don't think I have anything else to add but please, please, try to be a tiny bit open minded around this request: it would simplify and normalize by far interoperability with any present to future foreign PL dealing with JS: thank you!
I read that as: you need to try/catch ever single function running in any program ... that will create so much unnecessary GC pressure out of a WeakMap or WeakSet I can't even imagine ... but also ...
if I invoke the function without new or Reflect.construct it will not be side effect free if the function is meant to be used both as utility and as class instantiation (I've mentioned jQuery not by accident)
function jQueryLike(options, ...args) {
if (this instanceof jQueryLike) { return this }
// desired side-effects here
options.counter += 1;
return new jQueryLike(options, ...args);
}
If I mark that class as constructible and always use new after such mark I lose the ability to have possibly desired side-effects so this is not an option, is a hack, or a workaround, full of potential issues.
With this proposal: none of these issues exist ... no burden on developer shoulders, no hacks or workarounds needed ...