gilbert / es-explicit-this

Explicit this naming in ECMAScript functions
18 stars 2 forks source link

Whether support default value initializers for this parameter #8

Open hax opened 4 years ago

hax commented 4 years ago

I don't see much use cases of function f(this = value) {} in the wild, because it's impossible to invoke undefined.foo() anyway, and we already ban f() if f is using explicit this.

The only possible use case is foo::bar() when foo could be undefined, but I feel it bring confusion more than usefulness, and as previous discussion in bind/pipeline operators, it's likely we may want foo?::bar() for undefined/null cases (like ?.) in the future.

There are also some other issues:

  1. this = value is always invalid in all places, allow it in parameter may cause confusion.
  2. Non-strict functions behave like function f(this = this == null ? globalThis : this). So what's the accurate semantic of function f(this = value) {} if f is non-strict? It's a historical debt, theoretically we could revise the semantic for explicit this functions (will create separate issue for that), but I found all options have some weirdness in some way.

My current preference is do not support default value initializers for this parameter.

ljharb commented 4 years ago

f.call() has an undefined receiver. I'm confused why f() would be "banned" in any case?

hax commented 4 years ago

@ljharb Yes f.call() has an undefined receiver but I think f.call, f.apply or f.bind are some degree reflection APIs like Reflect.apply(f, undefined), programmers only use them for specific reasons, most time you use f.call because you want to send a specific this argument, for example, if we supported function f(this = value) you may want to use f.call(undefined) to use the default value, but it's the consequence not the reason. So I don't treat them as important use cases.

why f() would be "banned" in any case

This is proposed by @gilbert : "If a function has explicitly named its this parameter, it could be useful to throw an error when that function gets called without one" . I believe the intention is to avoid common mistakes of invoking methods without receiver.

ljharb commented 4 years ago

Personally, I find call/apply/bind use cases much more critical to support than any of the edge cases listed in the readme.

If the intention is to avoid that mistake, then it seems that providing a default value would be defeating that goal entirely.

hax commented 4 years ago

I find call/apply/bind use cases much more critical to support than any of the edge cases listed in the readme.

@ljharb What edge cases? There is no section named "edge cases" in the readme of this proposal :-P

If the intention is to avoid that mistake, then it seems that providing a default value would be defeating that goal entirely.

Not sure what you mean. Do you mean programmers can provide default value so that f() could just work?

It's possible, but I believe in most cases there is no proper default value programmers can provide. Actually most builtin methods would throw if do not provide receiver, for example (0, Promise.resolve)(1) would throw. On the other side, the history of non-strict functions (which use globalThis as default value) tell us default value for this is very likely a bad idea ^_^

ljharb commented 4 years ago

You’re right; i confused it with your other proposal, my apologies.

I mean that by providing a default value for this, the programmer has explicitly said that they don't want an undefined receiver to throw.

hax commented 4 years ago

the programmer has explicitly said that they don't want an undefined receiver to throw.

So

function f1(this) {}
function f2(this = foo) {}
f1() // throw
f2() // not throw

Seems possible! Though I hope we could find some good use cases for providing default value for this parameter.

And we also need to figure out what should happen on non-strict functions.

ljharb commented 4 years ago

Sloppy functions can’t ever see undefined/null as the receiver, so i wouldn’t expect that to change - a default value for the receiver in a sloppy function would never trigger.

hax commented 4 years ago

If never trigger, we'd better forbid it in syntax layer?

Since you can't write

function f(x = foo) {
  'use strict' // <-- syntax error
}

Which means when parser see function f(this = , it already know whether f is strict or non-strict, so it could report syntax error for non-strict.

hax commented 4 years ago

Sloppy functions can’t ever see undefined/null as the receiver, so i wouldn’t expect that to change

@ljharb I ask the question because there are two possible ways for function nonStrict(this = foo()) {}:

let o = Object(this ?? globalThis)
o = o === undefined ? foo() : o // never trigger
this = o

or

let v = this === undefined ? foo() : this
this = Object(v ?? globalThis)

I think you expect the first one, which never trigger default initializer.

I'm not sure whether people would expect the second.

ljharb commented 4 years ago

Yes, I’d expect the first one. People who want the second’s behavior can make their function strict, just like now.

hax commented 4 years ago

FYI, C# ban the default value for this parameter

Currently, TS also do not support default value for this parameter.

(Java don't support default value for all parameters at all.)