Some questions about Private Name Objects (Kevin Smith)

# Joseph Spencer (13 years ago)

I was going to say that I completely agree with Brendan on this:

Meanwhile we have lots of extant code that does things like

function foo(bar, baz) { baz = baz || default_baz; ... }

That's the cowpath we are paving.

As a javascript developer looking at this new feature, and really having never dealt with default parameter values very much outside of PHP (where you're pretty much limited to literals for default values), I would expect that default values in javascript would have complete access to the inner scope.

I think the approach of looking at default parameters as a way to avoid short-circuiting (as BE mentioned) makes a lot of sense, and is rather conservative.

Old way: function play(sound, player){ var _sound = sound || "boo"; var _player = player || new Player();

return _player;

function Player(){ this.play = function(){return _sound}; } }

New way: function play(sound = "boo", player = new Player()){

return player;

function Player(){ this.play = function(){return sound}; } }

It does seem slightly incomplete though. I mean, there still isn't a guarantee that the incoming type will match.

Old way: play(false, false).play() //error

New way: play(false, false).play() //error

Seems like one would still have to short-circuit, unless you provide a mechanism to validate the arguments.

I would expect, that on the initial declaration of the function, the type of the default value would be determined with some sort of pre-check (and possibly limiting the type to PrimaryExpressions). Then on any call, the incoming argument type would be evaluated against the default return type, else fallback to using the initial RHS return type for the value.

The problem with default parameter values that are evaluated at execution time, is that you need to provide a way for the RHS to validate the argument, otherwise one still may be required to validate incoming args anyway, thus defeating the purpose of default values.

-Joe

On Fri, 2012-09-14 at 15:05 -0700, Brendan Eich wrote:

Was there more? No uncited text from you.

/be

Joe Spencer wrote:

Sent from my Cricket smartphone

es-discuss-request at mozilla.org wrote:

Send es-discuss mailing list submissions to es-discuss at mozilla.org

To subscribe or unsubscribe via the World Wide Web, visit mail.mozilla.org/listinfo/es-discuss or, via email, send a message with subject or body 'help' to es-discuss-request at mozilla.org

You can reach the person managing the list at es-discuss-owner at mozilla.org

When replying, please edit your Subject line so it is more specific than "Re: Contents of es-discuss digest..."

Today's Topics:

  1. Re: Generator issue: exceptions while initializing arguments (Dmitry Soshnikov)
  2. Re: Generator issue: exceptions while initializing arguments (Kevin Smith)
  3. Re: Generator issue: exceptions while initializing arguments (Jason Orendorff)
  4. Re: Generator issue: exceptions while initializing arguments (Kevin Smith)
  5. Re: Some questions about Private Name Objects (Kevin Smith)

On Wed, Sep 12, 2012 at 5:13 AM, Brendan Eich<brendan at mozilla.com>

wrote:

You say it's too error prone but you don't adduce any evidence.

Meanwhile we have lots of extant code that does things like

function foo(bar, baz) { baz = baz || default_baz; ... }

That's the cowpath we are paving.

I see. Although, I cannot recall any other language which uses

complex

expressions (such as function calls, or even referencing to the

neighbor

parameters) for defaults. Usually they are just literals (no matter

which,

primitive or objects -- they are recreated every time at

activation).

But, I agree, they way you describe brings more power, and maybe JS

will be

the first language with such semantics for defaults.

If so, then it's just the matter of the specification to notice all

the

subtle cases.

function getDefaul() {}

function foo(bar = [], baz = bar, quz = getDefault()) { function getDefault() {} }

The first thing which will confuse the devs is that getDefault() expression is not the immediate function call to only initialize

the

default value of "quz", but is the syntactic form of calling this

function

every time to calculate the default value. This should be mentioned

very

explicitly, because from the top-down code viewing it really looks

like

getDefault() is just an initialize of the default, and in this

view, its

scope is even looks like the outer scope.

Second, of course the scope of all the defaults expressions, with

the bold

font: "Notice, that the scope of defaults evaluation is the body of

the

function. This is done to support referencing to the neighbor

arguments".

As for generators,

function createGen(foo = thrower()) { yield 1; }

var gen = createGen(); // loc1 gen.next(); // loc2

Of course the devs (again, w/o referencing to spec or detailed

docs), would

expect that the "thrower()" is called right away when the "createGen"

function is defined. After they have read the docs, and now know

that such

function calls are executed at function activation instead, they

would

expect that the "thrower()" is called at "loc1", when the function

is

activated to create the generator object.

OTOH, we understand the technical implementation, that the call to "createGen" may not execute the function body, and therefore, not

to call

"thrower()". And this is again will be too confusing for novices (?) that

it (by the technical details/implementation?) should/may be an "loc2".

So, if all these things described above are worth doing/supporting/explaining/etc., then we can build defaults based

on "foo

= foo || bar()" pattern which is used today. Then it's completely

OK, I

agree, it's powerful.

However, if to take new programmers which will join JS from now, potentially they should not know about old patterns for defaults as "foo =

foo || default", and they can use simple defaults as literals only.

In some/many languages the defaults are used with nulls only to

be able

to call a function with lesser parameters number. E.g. (PHP):

function foo($foo, $bar = null) { if (!$bar) { $bar = $this->getDefault(); } }

And now they can use foo(10), or foo(10, "bar"); But still, the

expression

which calculates the default $bar is described explicitly inside (in the

scope of) the function.

JS has no such problems, we may call foo(), or foo(10), or foo(10, "bar")

regardless default values, even today.

Also, the fact, that the expressions should be calculated at

activation,

not creation, comes from legacy, when function declarations should

be

created on entering the context. Then, even this code:

var foo = function () {};

function bar(baz = foo()) { ... }

Should not logically work, since "bar", being a FD, is created on

entering

the context, when "foo" is not function yet. This can satisfy the activation frame + activation time rules for such expressions.

Dmitry

We still have the choice of the implicit yield going after

argument

defaulting, not before. SpiderMonkey implements before, though,

and my own

argument from compositionality, above, makes it more clear to me

that we

should go with implicit-yield-first. This makes contrived throwing parameter default expressions throw "late" but if that matters,

use a

function combined with a generator.

In this thread, you and I have agreed on simpler

scope/hoisting/defaulting/

**initialisting a la ES1-5. I think on reflection that this argues

for

the implicit yield in a generator function going before anything

with

observable effects, including argument defaulting. Sorry to backtrack the thread, but this is going to cause riots.

function* g(a, b = makeTheWorldABetterPlace()) { ... }

let iter = g(123);
// Is the world a better place yet?  I hope so.
iter.next();

No amount of argument is going to convince a user that defaults

should

not be executed at the location of the call. That's just not how

it

reads. No?

Kevin

On Thu, Sep 13, 2012 at 8:23 PM, Kevin Smith<khs4473 at gmail.com>

wrote:

No amount of argument is going to convince a user that defaults

should not

be executed at the location of the call. That's just not how it

reads. No?

I kind of agree, but at this point we could do with a little less argument and a little more in the way of concrete proposals.

So here is a concrete proposal. Please improve on it; it’s an

honest

attempt, not a straw man in the rhetorical sense...

I think Allen is proposing a scheme where in code like

function f(a1=EXPR1, a2=EXPR2, a3=EXPR3) { ... }
f();

first EXPR1 is evaluated in a scope including only a1, then EXPR2 is evaluated in a scope that contains a1 and a2, then EXPR3 is evaluated in a scope that contains a1, a2, and a3, then a new environment is created for the function body, its vars

and

local functions.

If EXPR1 contains a closure that uses the name a2, that means the global a2, not the argument a2. Even if it gets called after a2 is bound.

In the spec language this would have to be modeled with four nested environments; a single mutable environment wouldn't work. In SpiderMonkey, if any of the EXPRs contained a closure that uses

with

or direct eval, we would have to actually reify one of the nested environments. Ugly but doable; any other implementors want to

comment?

There would have to be some specification hackery to make this work just like ES3 in cases where f also contains a var or function with the same name as an argument. But Allen can do it. :)

I don’t have a strong preference. It seems to be is a classic Right Thing vs. Worse Is Better situation. The advantage of the Worse Is Better approach is that you can correctly and completely explain what’s going on in one sentence: “It’s just like an if statement at the top of the function body.” It may not be reasonable, but

ordinary

developers can reason about it. When it comes to corner cases,

which

is more important?

-j

I don’t have a strong preference. It seems to be is a classic

Right

Thing vs. Worse Is Better situation. The advantage of the Worse Is Better approach is that you can correctly and completely explain what’s going on in one sentence: “It’s just like an if statement

at

the top of the function body.” It may not be reasonable, but

ordinary

developers can reason about it. When it comes to corner cases,

which

is more important?

Apologies for not making this clear earlier, but I agree with the "Worse is

Better" approach. That is, execute defaults in the scope of the

function

body. When I tried to formulate the scope boundaries (as you have)

it

became clear to me that it would be convoluted any other way.

What I'm questioning now is the idea that, in generators, the

implicit

first yield should occur before the defaults are evaluated. I

don't

think that's a tenable position. Is there an implementation

problem with

inserting the implicit first yield after the defaults are

evaluated but

before the rest of the function body?

Thanks for your time on this, BTW.

Kevin

The real point I'm trying to make is that Name objects give us

something

akin to clojure's protocols. Imagine an "orm" protocol -- this is

just a

set of names that must exist on an object (or its proto chain). An

object

can implement any number of protocols (or interfaces, or whatever)

without

fear of conflict. You can easily override any implementation so

long as you

have a handle on the appropriate name object. This is easier,

better

looking and more correct than anything we can do today. It's not

too

disimilar from using using instanceof as a brand, but without the

pitfalls

(it doesn't fall flat crossing sandbox boundaries). This is a safe

and

flexible inheritance model that works just as a js programmer

would expect,

all without begging for mutable or multiple prototypes.

I think this is a winning argument. So the problem becomes: how

can we

implement this in a non-fugly way? As it stands (and as Allen has

pointed

out), we don't currently have the syntax to make this work, even

for

"iterator":

import iterator from "sys:iterator"; // Sorry, hate @'s : )

class Derived extends Base {

  // Hmmm... No way to put iterator here.
}

// Try here?  Fugly...
X.prototype[iterator] = function() {

  super.doSomething(); // Oops - super outside of class

definition : (

};

The absolute minimum we need is a way to specify computed property

names in

classes (and what the heck, object literals too):

import iterator from "sys:iterator";

class Derived extends Base {

  [iterator]() {
    super.doSomething(); // Works like a charm!
  }
}

This is IMO the best way forward. It's generally useful beyond the

use

case presented here and does not use up a valuable free ASCII

character.

It also doesn't require any new symbolism since we already

understand that

brackets indicate computed property names. Why were computed

property