using Private name objects for declarative property definition.

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Jul 11 17:29:09 PDT 2011


On Jul 11, 2011, at 4:18 PM, Brendan Eich wrote:

> On Jul 11, 2011, at 12:40 PM, Allen Wirfs-Brock wrote:
> 
>> isGenerator is essentially a value test rather than class-like categorization.  Methods work well for this because a method can dynamically inspect the value being tested in order to make the determination.
> 
> I'm not so sure about this now. I was just reviewing with Dave how the design evolved. We had Function.isGenerator, analogous to Array.isArray. For taskjs, Dave had thought he had a use-case where the code has a function and wants to know whether it's a generator. It turned out (IIUC) that what he really wanted (this will warm your heart) was simply the duck-type "does it implement the iterator protocol" test.
> 
> On the other hand, code that wants to ask "is this *value* a generator?" may not have a-priori knowledge that the value is a function, so a class method such as Function.isGenerator wins.
> 
> Indeed Array.isArray, apart from being named as if it were a class method just to avoid polluting the global (or some other) object, is a predicate function -- not  a method. You can't know that you have an Array instance, never mind an object-type (typeof sense) value, all the time. If you don't know whether x is an array or even an object as opposed to a primitive, just call Array.isArray(x).
> 
> This strongly suggests to me that we want predicate functions. For the "@name" built-in module, isName is one such. There's no need to stick that into a "class object" just to avoid name pollution, since the module lets the importer name the import.
> 
> To rehash a subtle point again: our Function.prototype.isGenerator method is particular nasty if a user knows Array.isArray and expects Function.isGenerator(x) to test whether x is a generator (or any non-generator, whether function or not) value. That expression actually evaluates equivalently to Function.prototype.isGenerator.call(Function, x), and x is ignored -- and Function is of course not a generator.
> 
> So I think we took the wrong door here. Function.isGenerator by analogy to Array.isGenerator, or an isGenerator export from "@iter" (equivalent semantically), is the best way.


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).  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).  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.

> 
> 
>> 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.  If a value is expected to possibly be null or undefined then that probably represents a separate condition and should have a separate test.  If null/undefined is not an anticipate value then an exception on the property access is probably a fine dynamic error check.

> 
> 
>> 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. 

> 
> 
>> 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.  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.

> 
> 
>> We are trying to generalize to a pattern to apply to all (or at least most isFoo) situations.  Here is what we seem to have observed so far:
>> 
>> A isFoo method works well for value classification for situations where you will generally  already know the "class" of the value.
> 
> Agreed.
> 
> 
>> A 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.


> 
> 
>> 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.

Allen


More information about the es-discuss mailing list