using Private name objects for declarative property definition.

Brendan Eich brendan at mozilla.com
Mon Jul 11 18:14:28 PDT 2011


On Jul 11, 2011, at 5:29 PM, Allen Wirfs-Brock wrote:

> I was almost sold on this argument, but I see a different issue.  "Global" predicate functions like this aren't extensible.  Array.isArray and Function.isGenerator work fine because they are testing deep implementation level characteristics of special objects that can not be emulated by JS code (at least without proxies, in the case of Array).

True of isName and Proxy.isProxy (neé isTrapping) too.

I agree the duck-typing and class-tag test use-cases are different. I'm pretty sure we don't have any standard or proposed isDuck examples, though.


> However, for pure JS classification you want them to be duck-type extensible. It is easy to add a new implementation for some category if the category test uses an instance property classification property (whether method or data property) and perhaps with some monkey patching.   But a single global predicate can't be post facto extended to recognize new implementations of the category unless it was built with some internal extension mechanism (and any any such mechanism is likely to depend upon some per instance property, so that just loops us back to the same solution).

Why switch topics to pure JS classification, though? There are two different use-cases here.


>  I think you already brought this up earlier in this thread when you asked how would I extend Array.isArray to recognize a proxy based implementation of Array.

I did, but that's not the same case. Array.isArray is not asking a duck- or structural-typing question. It really wants all the magic, which can't be written down via recursive structural types. If you can make a Proxy instance that fools users and passes the Array-Turing-test, great! But then you have to reimplement Array.isArray at least -- perhaps even replace all of Array.


>>> However, methods are less desirable for class-like categorization because they require an existence predicated call (f.isFoo && f.isFoo()) which potentially leads to monkey patching Object.prototype (Object.prototype.isFoo = function(){return false}).  A truthy data property is a plausable alternative that avoids the need for monkey patching, but it doesn't work for value tests.
>> 
>> Only if you know you have an object. If the predicate you need has signature "any -> boolean" then you want a function.
> 
> All values except for null and undefined are automatically coerced to objects for property access.

Yes, null and undefined "count" -- especially since with an unknown value you are more likely to have undefined than 42 or "foo". Also, nasty host objects.

I don't think "close enough, ignore null and undefined" cuts it.


>  If a value is expected to possibly be null or undefined then that probably represents a separate condition and should have a separate test.

Why?

One can write any -> boolean functions. Many built-in operators and functions take any type input argument, not only non-null object arguments.

A restriction against null and undefined is arbitrary and it taxes all programmers who have to (remember to) write the extra test, instead of taxing the implementation to provide the predicate once for all.


>  If null/undefined is not an anticipate value then an exception on the property access is probably a fine dynamic error check.

Not for Array.isArray or the other built-ins we're discussing.

We shouldn't make arbitrary judgments against certain use-cases. Sometimes you know you have an object (or even a function), and a "narrower" input type for a predicate will do. Other times, you need the widest (any -> boolean) predicate. And the latter can work in the former cases without trouble. So why insist on object.method() with throw on null or undefined?


>>> If a value tests can be recast as a class-like categorization then the data property approach works for it.
>> 
>> Right, but you can't "recast" for any type of value without writing a prior typeof conjunct. Or did you mean something else by "recast"?
> 
> No, I don't mean a dynamic type cast.  I meant something like giving generator functions a distinct prototype so the isGenerator: true could be factored out of the individual instances. 

That does not handle the nominal type test you agree is the purpose of isGenerator, so I don't know why you're citing this use-case for the other, is-a-loose-duck-type case.


>>> Using an alternative prototype for all values in a "subclass" (eg all generators) seems like a technique that might be plausible in situations like this.  It is essentially just a way to factor out of each generator the storage of the true value for the isGenerator property.  It doesn't require the exposure of a separate Generator constructor. 
>> 
>> I think this misses the mark. Both Array.isArray in ES5 and Function.isGenerator in ES.next are testing nominal type tag. They are not testing some ad-hoc boolean "tag" that is not reliable and that can be forged.
> 
> I agree.  But we are trying extrapolate to a pattern that is applicable for any application defined classification scheme.

No, or at least: I'm not. Rather, I'm trying to find out whether there is one pattern, or two. I see at least one, the nominal tag test. That needs attention.

We can get to the other one, the loose foo.isDuck boolean data property or undefined falsy-value test, only if we really need to. I think you've made a good case for its extensible nature. But I'd rather leave it for JS users and library authors. And isGenerator has nothing to do with it.


> Those typically won't be deep nominal types with implementation dependencies.   It may simply be that isGenerator (and perhaps isArray) just isn't a good exemplar for the more general situation.

Whew! That's what I've been getting at.

But per my above words, I'd like to go further in seeking clarity about what *not* to do, as well as what to do, and what patterns to keep in mind for later (so I'm not complaining about taking the time to diagram the use-cases and solutions a bit). I simply don't see why we need the isDuck method pattern for any built-ins in ES5 or proposed for Hamony. And here I include elaboration of generators' prototype chains.



>>> An independent classification function (perhaps hung from a constructor) may be a good solution when value classification will generally be done in situations where the "class" of the value is not predetermined.
>> 
>> Agreed.
> 
> and the classification doesn't need to be extensible to new implementations.

Right, that's a good point. The isDuck scheme is extensible -- to a fault! :-P


>>> A truthy isFoo data property works will for class-like categorization as long as all values that share the same prototype are considered members of the same category.
>> 
>> Disagree for the use-cases you applied this to. ES5 mandates a [[Class]] check for Array.isArray, and the impetus there was cross-frame Array classification based on nominal really-truly-built-in-Array type tag.
>> 
>> Ditto for any isGenerator worth its name, that is, to the extent that the use-case for isGenerator does not simply want to test "is it an iterator factory", which could be a structural or duck-type test.
> 
> Agreed, these are both cases where the category isn't user extensible.  However, I think my statement holds for class-like categorization that are extensible.

Do we have any examples of those in the spec., or contemplated for ES.next?

/be


More information about the es-discuss mailing list