Proxy.isProxy (Was: using Private name objects for declarative property definition.)
Perhaps Proxy.isProxy was used merely as an example, but wasn't the consensus that Proxy.isProxy is not needed? Dave pointed out that it breaks transparent virtualization. Also, there is Object.isExtensible which always returns |true| for (trapping) proxies. That means we already have half of Proxy.isProxy without exposing proxies: if !Object.isExtensible(obj), obj is guaranteed not to be a proxy. Cheers, Tom 2011/7/9 Brendan Eich bren...@mozilla.com Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
I really don't think IsProxy is a good idea. It can lead to subtle bugs depending on whether an object is a DOM node, or a wrapper around a DOM node (or whether the embedding uses a proxy to implement DOM nodes or not). In Firefox we plan on making some DOM nodes proxies for example, but not others. I really don't think there is value in exposing this to programmers. Andreas On Jul 13, 2011, at 1:23 AM, Tom Van Cutsem wrote: Perhaps Proxy.isProxy was used merely as an example, but wasn't the consensus that Proxy.isProxy is not needed? Dave pointed out that it breaks transparent virtualization. Also, there is Object.isExtensible which always returns |true| for (trapping) proxies. That means we already have half of Proxy.isProxy without exposing proxies: if !Object.isExtensible(obj), obj is guaranteed not to be a proxy. Cheers, Tom 2011/7/9 Brendan Eich bren...@mozilla.com Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
I fully agree that isProxy sounds like a bad idea. It just breaks the proxy abstraction. /Andreas On 13 July 2011 10:26, Andreas Gal g...@mozilla.com wrote: I really don't think IsProxy is a good idea. It can lead to subtle bugs depending on whether an object is a DOM node, or a wrapper around a DOM node (or whether the embedding uses a proxy to implement DOM nodes or not). In Firefox we plan on making some DOM nodes proxies for example, but not others. I really don't think there is value in exposing this to programmers. Andreas On Jul 13, 2011, at 1:23 AM, Tom Van Cutsem wrote: Perhaps Proxy.isProxy was used merely as an example, but wasn't the consensus that Proxy.isProxy is not needed? Dave pointed out that it breaks transparent virtualization. Also, there is Object.isExtensible which always returns |true| for (trapping) proxies. That means we already have half of Proxy.isProxy without exposing proxies: if !Object.isExtensible(obj), obj is guaranteed not to be a proxy. Cheers, Tom 2011/7/9 Brendan Eich bren...@mozilla.com Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
And in general, the main use case for proxies is to emulate host objects. If there is a language construct that helps separating the two cases, we're going against this use case. David Le 13/07/2011 10:26, Andreas Gal a écrit : I really don't think IsProxy is a good idea. It can lead to subtle bugs depending on whether an object is a DOM node, or a wrapper around a DOM node (or whether the embedding uses a proxy to implement DOM nodes or not). In Firefox we plan on making some DOM nodes proxies for example, but not others. I really don't think there is value in exposing this to programmers. Andreas On Jul 13, 2011, at 1:23 AM, Tom Van Cutsem wrote: Perhaps Proxy.isProxy was used merely as an example, but wasn't the consensus that Proxy.isProxy is not needed? Dave pointed out that it breaks transparent virtualization. Also, there is Object.isExtensible which always returns |true| for (trapping) proxies. That means we already have half of Proxy.isProxy without exposing proxies: if !Object.isExtensible(obj), obj is guaranteed not to be a proxy. Cheers, Tom 2011/7/9 Brendan Eich bren...@mozilla.com mailto:bren...@mozilla.com Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. ___ es-discuss mailing list es-discuss@mozilla.org mailto:es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
isProxy is definitely a meta-layer operation and you don't want it polluting the application layer. However, when you doing proxy level meta programming I can imagine various situations where you might need to determine whether or not an object is a proxy. I think it should exist, but should exist in a namespace that is clearly part of the meta-layer. Arguably Proxy, itself, is such a namespace. But if there is fear that it is too close to the app layer then we might hand it from Proxy.Handler or something else that is clearly on the meta side. Or have a Proxy support modules. Allen On Jul 13, 2011, at 2:07 AM, David Bruant wrote: And in general, the main use case for proxies is to emulate host objects. If there is a language construct that helps separating the two cases, we're going against this use case. David Le 13/07/2011 10:26, Andreas Gal a écrit : I really don't think IsProxy is a good idea. It can lead to subtle bugs depending on whether an object is a DOM node, or a wrapper around a DOM node (or whether the embedding uses a proxy to implement DOM nodes or not). In Firefox we plan on making some DOM nodes proxies for example, but not others. I really don't think there is value in exposing this to programmers. Andreas On Jul 13, 2011, at 1:23 AM, Tom Van Cutsem wrote: Perhaps Proxy.isProxy was used merely as an example, but wasn't the consensus that Proxy.isProxy is not needed? Dave pointed out that it breaks transparent virtualization. Also, there is Object.isExtensible which always returns |true| for (trapping) proxies. That means we already have half of Proxy.isProxy without exposing proxies: if !Object.isExtensible(obj), obj is guaranteed not to be a proxy. Cheers, Tom 2011/7/9 Brendan Eich bren...@mozilla.com Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
Putting private properties on a proxy or storing it in a weak map are simple protocols you can use to keep track of proxies that you know about. You can hide or expose this information then without however many or few clients you like. If you want to give people access to knowledge about your proxy, you can share the private name object or weak map so that they can look it up, or even provide a similar predicate to isProxy. By contrast, if you want to virtualize an object with a proxy and we provide isProxy, we've made it almost impossible to protect the abstraction. It becomes a universal on-off switch that you can turn off by hiding the isProxy predicate (via module loaders or deleting/mutating the function). And to be even more concrete, if we want to use proxies for platform-level features, e.g. the DOM, then isProxy is something we *can't* turn off without violating the ECMAScript spec, so we're then *forced* to expose the implementation detail to anyone on the web who wants to look at it. Dave On Jul 13, 2011, at 8:31 AM, Allen Wirfs-Brock wrote: isProxy is definitely a meta-layer operation and you don't want it polluting the application layer. However, when you doing proxy level meta programming I can imagine various situations where you might need to determine whether or not an object is a proxy. I think it should exist, but should exist in a namespace that is clearly part of the meta-layer. Arguably Proxy, itself, is such a namespace. But if there is fear that it is too close to the app layer then we might hand it from Proxy.Handler or something else that is clearly on the meta side. Or have a Proxy support modules. Allen On Jul 13, 2011, at 2:07 AM, David Bruant wrote: And in general, the main use case for proxies is to emulate host objects. If there is a language construct that helps separating the two cases, we're going against this use case. David Le 13/07/2011 10:26, Andreas Gal a écrit : I really don't think IsProxy is a good idea. It can lead to subtle bugs depending on whether an object is a DOM node, or a wrapper around a DOM node (or whether the embedding uses a proxy to implement DOM nodes or not). In Firefox we plan on making some DOM nodes proxies for example, but not others. I really don't think there is value in exposing this to programmers. Andreas On Jul 13, 2011, at 1:23 AM, Tom Van Cutsem wrote: Perhaps Proxy.isProxy was used merely as an example, but wasn't the consensus that Proxy.isProxy is not needed? Dave pointed out that it breaks transparent virtualization. Also, there is Object.isExtensible which always returns |true| for (trapping) proxies. That means we already have half of Proxy.isProxy without exposing proxies: if !Object.isExtensible(obj), obj is guaranteed not to be a proxy. Cheers, Tom 2011/7/9 Brendan Eich bren...@mozilla.com Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
If you created the proxy, you can use a weak map to keep track of the fact that its a proxy (and even more precisely, its one of the proxies you created with your handler). Andreas On Jul 13, 2011, at 8:31 AM, Allen Wirfs-Brock wrote: isProxy is definitely a meta-layer operation and you don't want it polluting the application layer. However, when you doing proxy level meta programming I can imagine various situations where you might need to determine whether or not an object is a proxy. I think it should exist, but should exist in a namespace that is clearly part of the meta-layer. Arguably Proxy, itself, is such a namespace. But if there is fear that it is too close to the app layer then we might hand it from Proxy.Handler or something else that is clearly on the meta side. Or have a Proxy support modules. Allen On Jul 13, 2011, at 2:07 AM, David Bruant wrote: And in general, the main use case for proxies is to emulate host objects. If there is a language construct that helps separating the two cases, we're going against this use case. David Le 13/07/2011 10:26, Andreas Gal a écrit : I really don't think IsProxy is a good idea. It can lead to subtle bugs depending on whether an object is a DOM node, or a wrapper around a DOM node (or whether the embedding uses a proxy to implement DOM nodes or not). In Firefox we plan on making some DOM nodes proxies for example, but not others. I really don't think there is value in exposing this to programmers. Andreas On Jul 13, 2011, at 1:23 AM, Tom Van Cutsem wrote: Perhaps Proxy.isProxy was used merely as an example, but wasn't the consensus that Proxy.isProxy is not needed? Dave pointed out that it breaks transparent virtualization. Also, there is Object.isExtensible which always returns |true| for (trapping) proxies. That means we already have half of Proxy.isProxy without exposing proxies: if !Object.isExtensible(obj), obj is guaranteed not to be a proxy. Cheers, Tom 2011/7/9 Brendan Eich bren...@mozilla.com Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
On Jul 13, 2011, at 1:23 AM, Tom Van Cutsem wrote: Perhaps Proxy.isProxy was used merely as an example, That's right, it was one of several in seeking for naming conventions, and deeper distinctions among type-testing predicates. Not to worry -- I agree we should remove it. /be but wasn't the consensus that Proxy.isProxy is not needed? Dave pointed out that it breaks transparent virtualization. Also, there is Object.isExtensible which always returns |true| for (trapping) proxies. That means we already have half of Proxy.isProxy without exposing proxies: if !Object.isExtensible(obj), obj is guaranteed not to be a proxy. Cheers, Tom 2011/7/9 Brendan Eich bren...@mozilla.com Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
On Jul 13, 2011, at 8:44 AM, David Herman wrote: Putting private properties on a proxy or storing it in a weak map are simple protocols you can use to keep track of proxies that you know about. You can hide or expose this information then without however many or few clients you like. If you want to give people access to knowledge about your proxy, you can share the private name object or weak map so that they can look it up, or even provide a similar predicate to isProxy. I buy that a non-reflectable private name can be used to brand proxies. Coming from the perspective of a GC implementer, I'm suspicious of all arguments that are backstopped by requiring the use of weak maps, as their existence and size impose GC overhead. Yes, there are some things that really require them, but without knowing the volume and volatility of the objects involved it is hard to say whether or not weak maps is going to be an acceptable solution for any particular problem. By contrast, if you want to virtualize an object with a proxy and we provide isProxy, we've made it almost impossible to protect the abstraction. It becomes a universal on-off switch that you can turn off by hiding the isProxy predicate (via module loaders or deleting/mutating the function). We want to stratify reflective meta-programming. Violating the abstraction barrier of a proxy based abstraction is fine (and necessary), as long as you are operating in the meta strata. isProxy would clearly be a virtualization hazard if it existed on Proxy instances. But as one of the stratified proxy-related reflective operations it isn't. And to be even more concrete, if we want to use proxies for platform-level features, e.g. the DOM, then isProxy is something we *can't* turn off without violating the ECMAScript spec, so we're then *forced* to expose the implementation detail to anyone on the web who wants to look at it. So far, there is nothing in the ECMAScript spec. that says anything about whether or not Proxy's may be used to implement built-ins, host objects, or anything else and by implication what isProxy (if it exists) might report for such objects. I'd just as soon leave it that way. There are various ways that implementation details can get exposed. For example, by toString'ing a method property. I don't see why isProxy is any more of an abstraction leak than toString. It is actually less, if we clearly position it as one of the meta-programming functions that are available via the Proxy module. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proxy.isProxy (Was: using Private name objects for declarative property definition.)
There are various ways that implementation details can get exposed. For example, by toString'ing a method property. I don't see why isProxy is any more of an abstraction leak than toString. It is actually less, if we clearly position it as one of the meta-programming functions that are available via the Proxy module. I think this is a very weak argument. Just because we already have one leaky abstraction (toString) doesn't mean we should add additional ones. I prefer moving into the opposite direction: fix toString. Andreas Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On 9 July 2011 17:48, Brendan Eich bren...@mozilla.com wrote: Adding names as a distinct reference and typeof type, extending WeakMap to have as its key type (object | name), adds complexity compared to subsuming name under object. It seems to me that you are merely shifting the additional complexity from one place to another: either weak maps must be able to handle (object + name) for keys (instead of just object), or objects must handle (string + object * isName) as property names (instead of just string + name). Moreover, the distinction between names and proper objects will have to be deeply engrained in the spec, because it changes a fundamental mechanism of the language. Whereas WeakMaps are more of an orthogonal feature with rather local impact on the spec. (The same is probably true for implementations.) Why do you think that it would that make WeakMap more complicated? As far as I can see, implementations will internally make that very type distinction anyways. No, as proposed private name objects are just objects, and WeakMap implementations do not have to distinguish (apart from usual GC mark method virtualization internal to implementations) between names and other objects used as keys. And the spec also has to make it, one way or the other. Not if names are objects. I think an efficient implementation of names in something like V8 will probably want to assign different internal type tags to them either way. Otherwise, we'll need extra tests for each property access, and cannot specialise as effectively. I'm not sure which class you mean. The [[ClassName]] disclosed by Object.prototype.toString.call(x).slice(8,-1) is one possibility, which is one of the many and user-extensible ways of distinguishing among objects. ES.next class is just sugar for constructor/prototype patterns with crucial help for extends and super. I meant the [[Class]] property (I guess that's what you are referring to as well). Not sure what you mean when you say it is user-extensible, though. Is it in some implementations? (I'm aware of the somewhat scary note on p.32 of the spec.) Or are you just referring to the toString method? I appreciate the ongoing discussion, but I'm somewhat confused. Can I ask a few questions to get a clearer picture? 1. We seem to have (at least) a two-level nominal type system: the first level is what is returned by typeof, the second refines the object type and is hidden in the [[Class]] property (and then there is the oddball function type, but let's ignore that). Is it the intention that all type testing predicates like isArray, isName, isGenerator will essentially expose the [[Class]] property? 2. If there are exceptions to this, why? Would it make sense to clean this up? (I saw Allen's cleanup strawman, but it seems to be going the opposite direction, and I'm not quite sure what it's trying to achieve exactly.) 3. If we can get to a uniform [[Class]] mechanism, maybe an alternative to various ad-hoc isX attributes would be a generic classof operator? 4. What about proxies? Is the idea that proxies can *never* emulate any behaviour that relies on a specific [[Class]]? For example, I cannot proxy a name. Also, new classes can only be introduced by the spec. 5. What are the conventions by which the library distinguishes between regular object properties and operations, and meta (reflective) ones? It seems to me that part of the confusion(?) in the discussion is that the current design makes no real distinction. I think it is important, though, since e.g. proxies should be able to trap regular operations, but not reflective ones (otherwise, e.g. isProxy wouldn't make sense). Also, modern reflective patterns like mirrors make the point that no reflective method should be on the reflected object itself. Thanks, /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 12, 2011, at 2:54 AM, Andreas Rossberg wrote: On 9 July 2011 17:48, Brendan Eich bren...@mozilla.com wrote: Adding names as a distinct reference and typeof type, extending WeakMap to have as its key type (object | name), adds complexity compared to subsuming name under object. It seems to me that you are merely shifting the additional complexity from one place to another: either weak maps must be able to handle (object + name) for keys (instead of just object), or objects must handle (string + object * isName) as property names (instead of just string + name). Quite right. This was a part of ECMA-357, E4X, which we have suppported in SpiderMonkey starting in late 2004. It was a part of ES4 as well. On the other hand, there's no free lunch for private names having a new typeof type: name. They cannot be strings, so the VM's property lookup and identifier-handling code paths all need a wider type. True, they *could* be UUIDs in strings that have a tag bit set to prevent forgery, but that's not a likely implementation. So given that id types and lookup code paths have to change anyway, this seems more of a wash than you suggest. Not a total wash, but not a clear with for new typeof-type name. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
I think there is a (usually unstated) desire to also test for ES.next features that may also start to show up as extensions to ES5 level implementations. For example, generators in Firefox. You can't depend upon modules in such situations. For me, the issue is that ES engines tend to implement new language versions gradually (currently, still not everyone implementing ES5, yet some providing experimental features beyond ES5). That practice is good (gets useful features out there faster) and it matches the ES proposal wiki organization but JS language versioning on the web is not yet as modular (see the other thread for generators example, url below). I tend to think about language feature pragmas as a solution, but that is because I've seen that approach work in practice elsewhere. The idea of using ES/next modules to manage dependencies on language features is new, so I'm not sure it would work as well. It depends on two assumptions: - ES/next modules need to be the first ES/next feature to be implemented, so that the other features can be versioned using modules; that is, Firefox generators would only be available through modular versioning after adding modules - dependencies on language.feature modules have to be treated specially, to get useful error messages: usually, module dependencies are looked into after parsing, which is too late to complain about features that are not yet supported (I don't know how this interacts with transitive dependencies - might render the idea unworkable..) In other words, it is an ad-hoc idea about using module dependencies as language version pragmas, without having to add pragma syntax. It does have the advantage that modules have dependency management and early errors as part of their feature set already. The thread with subject Design principles for extending ES object abstractions got into this, and I *thikn* (I can't be sure because he hasn't replied yet) Luke Hoban, Dave Herman, and I were kinda/sorta agreeing that the old scripts (ES5 and below) need just one API entry point: SystemLoader as a global property (I misspelled it Object.ModuleLoader in my reply to Luke). Once old script has object-detected its way to joy via SystemLoader, it can load built-in ES6+ libraries such as @name and @iter. Thanks. My own single-message thread is here: feature-based and compartment-based language versioning https://mail.mozilla.org/pipermail/es-discuss/2011-July/015755.html From some of the messages in your thread it seems you have found yet another variation on mixing versions: using ES/next functionality while sticking to ES5 syntax (imperative access to some syntax-based declarative ES/next features). The API for module detection is under development, but it would allow both synchronous testing for a built-in or already-loaded MRL, and an async form for loading from an MRL over the 'net. Given this (forgive the lack of complete specs, but it's enough, I claim), why do we need anything more for future-friendly feature detection? 1. so that we can write code that wants to be ES/next, and get early failures with good error messages if that code tries to load on an engine that does not implement all ES/next features the code depends on. (not parse error at 'yield' but this engine does not implement generators yet, sorry) 2. so that we can write code that only depends on some ES/next features, and enable engines to figure out early if they are able to load and run this code, based on their partially implemented ES/next feature set. (if code depends only on generators, current Firefox may be able to run it, but we can't specify a dependency on only that feature) 3. so that we can selectively enable experimental language features, which will continue to be implemented prior to standardization, even after ES/next. (if code depends on generators, but should not be used in combination with some of the other experimental features in Firefox, we cannot specify such a selection at the moment) 4. So that coders can document their language feature dependencies in their source code (as page scripting is replaced by JS applications, and script files are replaced by modules). Claus ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 12, 2011, at 7:43 AM, Brendan Eich wrote: On the other hand, there's no free lunch for private names having a new typeof type: name. They cannot be strings, so the VM's property lookup and identifier-handling code paths all need a wider type. True, they *could* be UUIDs in strings that have a tag bit set to prevent forgery, but that's not a likely implementation. So given that id types and lookup code paths have to change anyway, this seems more of a wash than you suggest. Not a total wash, but not a clear with for new typeof-type name. Er, not a clear win Another point about implementations: any that perform at all well already use a sum type for their internal property id representation, to accomodate int or uint *or* string. Adding another arm to that discriminated union is not a huge amount of work, in our experience. For JavaScriptCore, Oliver has argued that internal to the engine, keeping id uncoerced as an arbitrary value, until late or deep in the layering, is not troublesome and could allow better true array or vector indexing semantics, e.g. via proxies. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 12, 2011, at 2:54 AM, Andreas Rossberg wrote: On 9 July 2011 17:48, Brendan Eich bren...@mozilla.com wrote: Moreover, the distinction between names and proper objects will have to be deeply engrained in the spec, because it changes a fundamental mechanism of the language. Whereas WeakMaps are more of an orthogonal feature with rather local impact on the spec. Not a big deal for the spec. One short ToPropertyKey algorithm in clause 9 and calls to it in a handful of places. I think an efficient implementation of names in something like V8 will probably want to assign different internal type tags to them either way. Otherwise, we'll need extra tests for each property access, and cannot specialise as effectively. Isn't that pretty much a wash either way I'm not sure which class you mean. The [[ClassName]] disclosed by Object.prototype.toString.call(x).slice(8,-1) is one possibility, which is one of the many and user-extensible ways of distinguishing among objects. ES.next class is just sugar for constructor/prototype patterns with crucial help for extends and super. I meant the [[Class]] property (I guess that's what you are referring to as well). Not sure what you mean when you say it is user-extensible, though. Is it in some implementations? (I'm aware of the somewhat scary note on p.32 of the spec.) Or are you just referring to the toString method? Because Table 8 says all objects (including host objects) have a string valued [[Class]] property, some people and third party specifications (and perhaps some implementations) have assumed that [[Class]] is an specified extension mechanism for Object.prototype.toString. To eliminate any such confusion, in the initial draft for the 6th edition spec. I have eliminated [[Class]] and replaced it with non-parameterized object brands using distinct internal properties. I will make that draft available within the next 24 hours. I appreciate the ongoing discussion, but I'm somewhat confused. Can I ask a few questions to get a clearer picture? 1. We seem to have (at least) a two-level nominal type system: the first level is what is returned by typeof, the second refines the object type and is hidden in the [[Class]] property (and then there is the oddball function type, but let's ignore that). You can certainly read it that way. The real nominal types of ECMAScript are defined in clause 8. Only the ECMAScript language types subset are exposed to ECMAScript programmers. The specification types subset are just that, specification devices. One of these nominal types is object. Specific kinds of objects are classified and discriminated in various ways within the specification (including via the value of [[Class]]). If you wish to thing of these categorization schemes as nominal types you can but the specification does not define them as such. In particular, it does not define any rules for forming such nominal types and it does not impose any orthogonality requirements on the categorization schemes. Essentially, this is an ad hoc layer that has been used for specification convenience. Is it the intention that all type testing predicates like isArray, isName, isGenerator will essentially expose the [[Class]] property? Absolutely not. But note that that these are not type testing in the sense that ES uses the work type. They discriminate different kinds (categorizations) of objects but ES does not define these as types. This is not an unusual dichotomy. For example, if you define Integer to be a type, what do you consider odd integers to be? Another layer of typing or just a further subcategorization of the member of the Integer type. 2. If there are exceptions to this, why? Would it make sense to clean this up? (I saw Allen's cleanup strawman, but it seems to be going the opposite direction, and I'm not quite sure what it's trying to achieve exactly.) see the associated google doc: https://docs.google.com/document/d/1sSUtri6joyOOh23nVDfMbs1wDS7iDMDUFVVeHeRdSIw/edit?hl=en_USauthkey=CI-FopgC [[Class]] has caused all sorts of confusion in the realm of host objects. It's use in the spec. has also resulted in some unnecessary over specification that gets in the way extensibility. 3. If we can get to a uniform [[Class]] mechanism, maybe an alternative to various ad-hoc isX attributes would be a generic classof operator? What is it you want to classify? What is a class? There is an infinite variety of ways that ECMAScript objects might be classified. No single one of them is universal. The built-in objects can present some exemplar classification patterns. But the built-ins actually represent a very small set of objects and use cases. We need to be careful about over generalizing from that small sample. 4. What about proxies? Is the idea that proxies can *never* emulate any behaviour that relies on a specific [[Class]]? For example, I cannot proxy
Re: using Private name objects for declarative property definition.
Allen, thanks for replying -- I ran out of time this morning. A few comments on top of yours. On Jul 12, 2011, at 10:18 AM, Allen Wirfs-Brock wrote: I think an efficient implementation of names in something like V8 will probably want to assign different internal type tags to them either way. Otherwise, we'll need extra tests for each property access, and cannot specialise as effectively. Isn't that pretty much a wash either way Not only that (I argued in my reply), but engines do already tag identifier types. Indexing does not use strings 0, 1, etc., even interned strings, in engines that aspire to be fast. Such (interned) strings must of course be observably equivalent to 0, 1, etc. when used as indexes and preserved as type-tagged integer domain property identifiers. I meant the [[Class]] property (I guess that's what you are referring to as well). Not sure what you mean when you say it is user-extensible, though. Is it in some implementations? (I'm aware of the somewhat scary note on p.32 of the spec.) Or are you just referring to the toString method? Because Table 8 says all objects (including host objects) have a string valued [[Class]] property, some people and third party specifications (and perhaps some implementations) have assumed that [[Class]] is an specified extension mechanism for Object.prototype.toString. To eliminate any such confusion, in the initial draft for the 6th edition spec. I have eliminated [[Class]] and replaced it with non-parameterized object brands using distinct internal properties. I will make that draft available within the next 24 hours. Just to amplify Allen's reply, we are working on dom.js (https://github.com/andreasgal/dom.js), the self-hosted DOM. Per its specs, it must be able in some future JS edition to configure the class name reported by Object.prototype.toString.call(x, 8, -1). 1. We seem to have (at least) a two-level nominal type system: the first level is what is returned by typeof, the second refines the object type and is hidden in the [[Class]] property (and then there is the oddball function type, but let's ignore that). You can certainly read it that way. The real nominal types of ECMAScript are defined in clause 8. Only the ECMAScript language types subset are exposed to ECMAScript programmers. The specification types subset are just that, specification devices. The typeof distinction is among primitive types with by-value semantics (null, undefined, boolean, number, string) and object, which as discussed recently has by-reference semantics, observable because objects are in general mutable. Among the objects, there are many and non-hierarchically-related classification schemes, as Allen reminds us (worth doing to avoid making brittle, crappy, tyrannical nominal hierarchies :-/). There isn't much nominal here in an untyped language. Perhaps we should use branding or trademarking, but when I use nominal in connection with JS I mean the same thing: certain built-ins have internal [[Class]] properties which not only contribute to Object.prototype.toString's result, the [[Class]] values are checked aggressively in built-in methods of these objects, such that the methods cannot in general be invoked on objects with other [[Class]] values. In engine implementations, C++ nominal types are often used, but the JS dispatching machinery does not reflect via C++ RTTI or any such thing. Instead, something corresponding to [[Class]] is compared to a known singleton, to enforce that these non-generic methods are called on the correct type of |this|. To sum up, typeof is for value or primitive vs. object type identification. When typeof x === object, x may then be classified in many ways. As a rule, and I believe the committee agrees on this, we wish built-ins to be generic where practical for implementations to unify representations so as to avoid the [[Class]]-equivalent nominal type tag, AKA brand or trademark, test. A proposal for Harmony gets into surfacing branding in the language: http://wiki.ecmascript.org/doku.php?id=strawman:trademarks This didn't make ES.next. (To repeat a minor comment I gave at the May TC39 meeting, prefigured above, I hope we can try to use one word, ideally as short as brand or branding, for this nominal type tag idea.) Is it the intention that all type testing predicates like isArray, isName, isGenerator will essentially expose the [[Class]] property? Absolutely not. But note that that these are not type testing in the sense that ES uses the work type. They discriminate different kinds (categorizations) of objects but ES does not define these as types. This is not an unusual dichotomy. For example, if you define Integer to be a type, what do you consider odd integers to be? Another layer of typing or just a further subcategorization of the member of the Integer type. However, as my discussion with Allen brought out (I
Re: using Private name objects for declarative property definition.
On Jul 12, 2011, at 11:08 AM, Brendan Eich wrote: 5. What are the conventions by which the library distinguishes between regular object properties and operations, and meta (reflective) ones? It seems to me that part of the confusion(?) in the discussion is that the current design makes no real distinction. I think it is important, though, since e.g. proxies should be able to trap regular operations, but not reflective ones (otherwise, e.g. isProxy wouldn't make sense). Also, modern reflective patterns like mirrors make the point that no reflective method should be on the reflected object itself. This is what we are in the process of sorting out. The ES spec. (and language) wasn't originally designed from that perspective but I think we can move it there. Definitely! I was in a big rush. I recall putting in primitive types (something I've repented at leisure) for two reasons: (1) be like Java (this wasn't just superficial, we were planning the JS/Java bridge, LiveConnect, even from the start in 1995); (2) ease of implementation (arguably I was biasing toward a certain kind of implementation, but it was what I knew how to do fast: fat discriminated union value types, hardcoded arithmetic for the NUMBER discriminant). For functions, I made a distinct typeof result so that users could determine callability (remember, there was no try/catch/finally in JS1 and anyway, try around a call attempt is too heavyweight). Among objects apart from functions, I created the prototype.constructor back-link but otherwise left it up to object implementors to come up with classification schemes. A bit more on the history of reflective facilities added at the base layer: For JS1 there was zero time to do proper mirrors-style reflective APIs. Economy of implementation and expression, plus the utility of objects as hashes, led me to make o[e] evaluate e into a result and equate it to a string property name. Even in the first implementation, which had some goofy bugs, I int-specialized for speed. From AWK (capitalized like a fanboy, after its creators' surname initials) I copied for-in as the matching way of enumerating the keys in the object-as-hash. So at base level, one can reflect on what other languages do not disclose without mirrors: the names of properties and, given any expression, the value of a property named by the string conversion of the result of evaluating that expression. ES3 added in, instanceof (we've talked about how this went wrong in the face of multiple globals), and a paltry (six, IIRC) Object.prototype reflective methods, some of which seem misdesigned (e.g. propertyIsEnumerable does not have own in its name but does not look up the proto chain). This was just shortest-path evolution in TC39 TG1, mostly, although I seem to recall prototyping in in SpiderMonkey before ES3 was far along. Allen's work with mirrors shows how rich and multifarious the reflection API world can be. This is good work and we need more of it. I don't think we should prematurely standardize mirror-style reflective APIs. We do need to correct course on reflective and other API design path missteps where we can. Waldemar made this point about standards processes: they can cycle faster but create path dependencies and enshrine mistakes, painting the language into a corner. Cycling too slow is bad too, obviously -- we have been there and done that (it's why we at Mozilla added so many things like getters and setters, __proto__, Array extras back during the dark ages -- we had no choice with a monopoly shutdown of the standards process, and taking that lying down is not good in itself or obviously better than making de-facto standards). Cycling at a pace that fits the Ecma-ISO protocols can work but leaves us with mistakes small (we hope) enough to live with, but sometimes worth fixing (we should consider each such potential mistake). I know Allen feels this regarding Object.create, specifically the property descriptor attribute defaults and consequent verbosity when describing typical properties. I feel it of course over all the mistakes in most of the language that I've created or had a hand in (or where I took time off to found mozilla.org, and did not help ES3). I'm not guilty at this advanced age, just resolved to fix what can be fixed. Mistakes happen, we need to be careful -- but we aren't going to do anyone any favors by sitting and thinking on how to achieve perfection -- so we ought to make corrections as we go. Backward compatibility then means some redundancy over time. There are much worse problems to have. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On 9 July 2011 14:42, Sam Tobin-Hochstadt sa...@ccs.neu.edu wrote: Unlike Names, strings and numbers are forgeable, so if they were allowed as keys in WeakMaps, the associated value could never be safely collected. Names, by contrast, have identity. Of course you are right, and I shouldn't post in the middle of the night. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 10, 2011, at 6:58 PM, Brendan Eich wrote: On Jul 10, 2011, at 10:54 AM, Allen Wirfs-Brock wrote: Why not a non-writable,non-enumerable non-configurable data property on Function.prototype. We're talking about isGenerator, right? There is no Generator constructor, no shadowing opportunity. Function can create a generator if yield occurs in the body string. Oops, forgot we were talking about isGenerator. So why not internally defined const __generatorProto__ = Object.create(Function.prototype, {isGenerator: {value: true}}); and specify that function containing yield are created as if by: __generatorProto__ | function (...) {...yield...} In other words, generator functions have a different [[Prototype]] than regulator functions. Alternatively, isGenerator could be a method, but that's the the point here. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 9:25 AM, Allen Wirfs-Brock wrote: On Jul 10, 2011, at 6:58 PM, Brendan Eich wrote: On Jul 10, 2011, at 10:54 AM, Allen Wirfs-Brock wrote: Why not a non-writable,non-enumerable non-configurable data property on Function.prototype. We're talking about isGenerator, right? There is no Generator constructor, no shadowing opportunity. Function can create a generator if yield occurs in the body string. Oops, forgot we were talking about isGenerator. So why not internally defined const __generatorProto__ = Object.create(Function.prototype, {isGenerator: {value: true}}); and specify that function containing yield are created as if by: __generatorProto__ | function (...) {...yield...} In other words, generator functions have a different [[Prototype]] than regulator functions. There's no point. We haven't ever had a request for this. It requires polluting the global object (or some object) with a property (even adding a private name requires adding a public property naming the private name object). Alternatively, isGenerator could be a method, but that's the the point here. That's the point we're arguing about, yes. The method allows object-detection, which helps. It's ad-hoc but web JS has versioned itself based on object detection far better than on other, _a priori_ grand plans (Accept: headers, e.g.). /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 9:32 AM, Brendan Eich wrote: On Jul 11, 2011, at 9:25 AM, Allen Wirfs-Brock wrote: On Jul 10, 2011, at 6:58 PM, Brendan Eich wrote: On Jul 10, 2011, at 10:54 AM, Allen Wirfs-Brock wrote: Why not a non-writable,non-enumerable non-configurable data property on Function.prototype. We're talking about isGenerator, right? There is no Generator constructor, no shadowing opportunity. Function can create a generator if yield occurs in the body string. Oops, forgot we were talking about isGenerator. So why not internally defined const __generatorProto__ = Object.create(Function.prototype, {isGenerator: {value: true}}); and specify that function containing yield are created as if by: __generatorProto__ | function (...) {...yield...} In other words, generator functions have a different [[Prototype]] than regulator functions. There's no point. We haven't ever had a request for this. It requires polluting the global object (or some object) with a property (even adding a private name requires adding a public property naming the private name object). I don't think we or anybody else has really explored the extensibility implications of generator functions so it isn't surprising that there have been no requests. Certainly there is no need to add any new globals to support a distinct prototype for generator functions. Strictly speaking there wouldn't even have to be a property on Function to access it as it would be a built-in object that is accessed internally when a generator is created. However, I do think it would be useful make it accessible via something like Function.generatorPrototype. I don't see that adding such a property as undesirable pollution. Alternatively, isGenerator could be a method, but that's the the point here. That's the point we're arguing about, yes. I think that the factoring of the prototype hierarchy is largely distinct issue but, yes, there is some overlap in that a use of a data property for this particular tagging requires a separate prototype object for generators. The method allows object-detection, which helps. It's ad-hoc but web JS has versioned itself based on object detection far better than on other, _a priori_ grand plans (Accept: headers, e.g.). Function.generatorPrototype as described above would also support object-detection. In general, how far to you think we can push a all new features have to be object-detectable requirement. Do we need to be able to object-detect |. How about rest and spread, or de-structuring? We are going to use non-eval detectability as a ECMAScript extension design criteria then maybe we do need a less ad-hoc scheme for feature detection. It wouldn't have to be all that grand... allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 10:25 AM, Allen Wirfs-Brock wrote: I don't think we or anybody else has really explored the extensibility implications of generator functions so it isn't surprising that there have been no requests. We certainly have explored generators (extensibility is not a good in itself) since 2006. Multiple libraries on github use them. Certainly there is no need to add any new globals to support a distinct prototype for generator functions. Strictly speaking there wouldn't even have to be a property on Function to access it as it would be a built-in object that is accessed internally when a generator is created. However, I do think it would be useful make it accessible via something like Function.generatorPrototype. I don't see that adding such a property as undesirable pollution. We're still trying to get isFoo conventions in order. Why add another (first-of or one-off) built-in prototype convention? I'm going to stick with YAGNI and when in doubt, leave it out. Generators win to the extent that they are functions with the least shallow continuation semantics on top that work. The method allows object-detection, which helps. It's ad-hoc but web JS has versioned itself based on object detection far better than on other, _a priori_ grand plans (Accept: headers, e.g.). Function.generatorPrototype as described above would also support object-detection. In general, how far to you think we can push a all new features have to be object-detectable requirement. Do we need to be able to object-detect |. It's easy to go overboard. Developers do not detect at fine grain to handle insane or never-shipped combinations. The combinatorics explode, and reality is that browsers support feature cohorts that are fairly chunky. How about rest and spread, or de-structuring? We are going to use non-eval detectability as a ECMAScript extension design criteria then maybe we do need a less ad-hoc scheme for feature detection. It wouldn't have to be all that grand... Even less grand ones such as the DOM's (http://www.w3.org/TR/DOM-Level-3-Core/core.html#DOMFeatures) are failures. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
How about rest and spread, or de-structuring? We are going to use non-eval detectability as a ECMAScript extension design criteria then maybe we do need a less ad-hoc scheme for feature detection. It wouldn't have to be all that grand... Even less grand ones such as the DOM's (http://www.w3.org/TR/DOM-Level-3-Core/core.html#DOMFeatures) are failures. Just noticed this hidden sub-thread, which seems to be related to my attempt to start a thread on modular versioning. How about the following attempt at least-grandness?-) - have a built-in module hierarchy 'language', with submodules for each new ES/next feature that is implemented in the current engine (language.destructuring, language.generators, ..) - code that relies on one of those features can try to import the language.feature module, and will be rejected early if that feature module isn't available - there could be collective modules corresponding to language versions (ES5, JS1.7, ES/next, ..), which behave as if they imported the language feature modules corresponding to that language version - this would suggest that ES/next modules ought to be the first ES/next feature to be implemented, to support versioned addition of the remaining ES/next features; but since modules already need to manage dependencies, there is no need to replicate that functionality for language features Makes sense? Claus ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 10:41 AM, Brendan Eich wrote: On Jul 11, 2011, at 10:25 AM, Allen Wirfs-Brock wrote: Certainly there is no need to add any new globals to support a distinct prototype for generator functions. Strictly speaking there wouldn't even have to be a property on Function to access it as it would be a built-in object that is accessed internally when a generator is created. However, I do think it would be useful make it accessible via something like Function.generatorPrototype. I don't see that adding such a property as undesirable pollution. We're still trying to get isFoo conventions in order. Why add another (first-of or one-off) built-in prototype convention? I'm going to stick with YAGNI and when in doubt, leave it out. Generators win to the extent that they are functions with the least shallow continuation semantics on top that work. Let's try to back out of the brambles a bit: On Jul 10, 2011, at 6:58 PM, Brendan Eich wrote: On Jul 10, 2011, at 10:54 AM, Allen Wirfs-Brock wrote: yes. I wonder if isArray and isInteger are different kinds of categorizations (class based vs value based) that perhaps should have distinct naming conventions. The isFoo predicate convention is used variously in other languages. Kind of like foop in Lisps. I would not tax name length just to distinguish these cases, or others (ontology fanatics will soon be splitting sub-cases of value-based). 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. 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. If a value tests can be recast as a class-like categorization then the data property approach works for it. 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. 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. 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. 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. Allen___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 11:50 AM, Claus Reinke wrote: How about rest and spread, or de-structuring? We are going to use non-eval detectability as a ECMAScript extension design criteria then maybe we do need a less ad-hoc scheme for feature detection. It wouldn't have to be all that grand... Even less grand ones such as the DOM's (http://www.w3.org/TR/DOM-Level-3-Core/core.html#DOMFeatures) are failures. Just noticed this hidden sub-thread, which seems to be related to my attempt to start a thread on modular versioning. How about the following attempt at least-grandness?-) - have a built-in module hierarchy 'language', with submodules for each new ES/next feature that is implemented in the current engine (language.destructuring, language.generators, ..) - code that relies on one of those features can try to import the language.feature module, and will be rejected early if that feature module isn't available - there could be collective modules corresponding to language versions (ES5, JS1.7, ES/next, ..), which behave as if they imported the language feature modules corresponding to that language version - this would suggest that ES/next modules ought to be the first ES/next feature to be implemented, to support versioned addition of the remaining ES/next features; but since modules already need to manage dependencies, there is no need to replicate that functionality for language features Makes sense? Claus I think there is a (usually unstated) desire to also test for ES.next features that may also start to show up as extensions to ES5 level implementations. For example, generators in Firefox. You can't depend upon modules in such situations. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 12:46 PM, Allen Wirfs-Brock wrote: I think there is a (usually unstated) desire to also test for ES.next features that may also start to show up as extensions to ES5 level implementations. For example, generators in Firefox. You can't depend upon modules in such situations. The thread with subject Design principles for extending ES object abstractions got into this, and I *thikn* (I can't be sure because he hasn't replied yet) Luke Hoban, Dave Herman, and I were kinda/sorta agreeing that the old scripts (ES5 and below) need just one API entry point: SystemLoader as a global property (I misspelled it Object.ModuleLoader in my reply to Luke). Once old script has object-detected its way to joy via SystemLoader, it can load built-in ES6+ libraries such as @name and @iter. The API for module detection is under development, but it would allow both synchronous testing for a built-in or already-loaded MRL, and an async form for loading from an MRL over the 'net. Given this (forgive the lack of complete specs, but it's enough, I claim), why do we need anything more for future-friendly feature detection? /be___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
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. 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. 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? 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. 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. 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. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
Adding a non-enumerable Array.prototype method seems doable to me, if the name is clear and not commonly used. We can probably still add Array.prototoype.isArray if that would help to establish the pattern. Document as being preferred over Array.isArray This doesn't make sense to me. Let's say I have a variable x and it's bound to some value but I don't know what. That is, it has the type any. I could check to see that it's an object: typeof x === object and then that it has an isArray method: typeof x.isArray === function but how do I know that this isArray method has the semantics I intend? I could check that it's equal to Array.prototype.isArray, but that won't work across iframes. IOW, I can't call x.isArray() to find out what I want it to tell me until I already know the information I want it to tell me. Alternatively, if I expect isArray to be a boolean rather than a predicate, I still don't have any way of knowing whether x is of a type that has an entirely different semantics for the name isArray. It's anti-modular to claim a universal semantics for that name across all possible datatypes in any program ever. These static predicates (which are just glorified module-globals) intuitively have the type: any - boolean:T (I'm using notation from SamTH's research here.) This means a function that takes any value whatsoever and returns a boolean indicating whether the result is of the type T. Because they accept the type any, it doesn't make sense to put them in an inheritance hierarchy. It makes sense to have them as functions. Since globals are out of the question, in the past they've been made into statics. But with modules, we can actually make them functions in their appropriate modules. Dave ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 4:18 PM, Brendan Eich wrote: So I think we took the wrong door here. Function.isGenerator by analogy to Array.isGenerator, ... by analogy to Array.isArray, of course. /be or an isGenerator export from @iter (equivalent semantically), is the best way. 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. 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? 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. 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. 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. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
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. Right-- it was a poor initial implementation decision on my part; I shouldn't have done any test at all. JJB had a different use case: for Firebug, he wanted the ability to check before calling a function whether it was going to have a different control-flow semantics. IOW, he wanted to be able to reliably reflect on values. 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. Yeah, I think it's generally more convenient for type-distinguishing predicates to have input type 'any', so that clients don't have to do any other tests beforehand. That way, you can always simply do isGenerator(x) instead of typeof x === function x.isGenerator() without knowing anything about x beforehand. So I think we took the wrong door here. Function.isGenerator by analogy to Array.isArray, or an isGenerator export from @iter (equivalent semantically), is the best way. I agree. Dave ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 4:19 PM, David Herman wrote: Adding a non-enumerable Array.prototype method seems doable to me, if the name is clear and not commonly used. We can probably still add Array.prototoype.isArray if that would help to establish the pattern. Document as being preferred over Array.isArray This doesn't make sense to me. Let's say I have a variable x and it's bound to some value but I don't know what. That is, it has the type any. I could check to see that it's an object: typeof x === object and then that it has an isArray method: typeof x.isArray === function but how do I know that this isArray method has the semantics I intend? I could check that it's equal to Array.prototype.isArray, but that won't work across iframes. IOW, I can't call x.isArray() to find out what I want it to tell me until I already know the information I want it to tell me. Alternatively, if I expect isArray to be a boolean rather than a predicate, I still don't have any way of knowing whether x is of a type that has an entirely different semantics for the name isArray. It's anti-modular to claim a universal semantics for that name across all possible datatypes in any program ever. In fact, this is exactly how polymorphic reuse and extensibility occurs in dynamically typed OO languages. And, for the most part it actually works even in quite complete programs. You aren't assigning a universal semantics to a name in all programs but you are asserting a contextual semantics for this particular program. Even within a single program, different objects can assign complete different and unrelated meanings to isFoo as long as the different objects never need to both be processed by common code that does a isFoo test. In practice, if such objects invalidly cross paths, any wrong answers from classification methods are usually easy to spot because they quickly lead to other dynamically detected failures. Once that occurs there typically are easily observable differences between the objects that make the problem easy to find. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
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
Re: using Private name objects for declarative property definition.
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
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 6:14 PM, Brendan Eich wrote: On Jul 11, 2011, at 5:29 PM, Allen Wirfs-Brock wrote: 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. Well, I thought we were trying to find a pattern that was applicable to both: On Jul 9, 2011, at 11:19 AM, Allen Wirfs-Brock wrote: A consideration here is that whatever conventions we follow sets a precedent for user written code. I don't think we want to encourage the addition of such classification functions to Object or Object.prototype. So from that perspective Array.isArray, Function.isGenerator, and Proxy.isProxy are better exemplars than Object.isArray, etc. Maybe we weren't as user written code normally does define these sorts of implementation classifications. However, the fact it took this long for us (well, at least me) to see that probably is a good indication that this distinction isn't always an obvious one. 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? When used as an explicit marker then there is probably explicit logic to deal with whatever the marker represents. The test for that condition can precede any other classification test. If they are being used intentionally then as such a marker then property accessing dereferencing them is probably an unintended error situation. One can write any - boolean functions. Many built-in operators and functions take any type input argument, not only non-null object arguments. Regardless of whether or not that was a wise design choice, it is precedent that we do need to consider. 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. Yes, but since we seem to be circling in predicate functions for those tests it's not an issue for them. 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? Well, this may be a style point that we can't agree on. I do think, that undefined and null are special cases that probably need to be explicitly dealt within when their use is intentional. 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. Because I got confused about what problem we were trying to solve. Also, like I said above, the distinction between the two use
Re: using Private name objects for declarative property definition.
On Jul 11, 2011, at 7:31 PM, Allen Wirfs-Brock wrote: 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? See http://wiki.ecmascript.org/doku.php?id=strawman:es5_internal_nominal_typing #8: In JSON.stringify, use a isJSONArray property to control output in array literal syntax and eliminate corresponding [[Class]] check also see https://docs.google.com/document/d/1sSUtri6joyOOh23nVDfMbs1wDS7iDMDUFVVeHeRdSIw/edit?hl=enauthkey=CI-FopgCpli=1 : The built-in method Array.prototype.concat (15.4.4.4) tests each of its arguments for [[Class]] equal to “Array” to determine whether the argument should be concatenated as a single object or whether it should be considered a transparent container of objects that are individually concatenated. Because it is a class test it is context independent. This test is problematic in that prevents used defined array-like objects from have this same transparent container behavior. This applied even to objects that have the Array prototype on their prototype chain. This transparent container treatment has no fundamental dependency upon the actual unique semantics of built-in arrays. It should be replaced with a check for some user-definable flag property that enables/disables transparent container treatment. Allen___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 9, 2011, at 7:22 PM, Brendan Eich wrote: On Jul 9, 2011, at 5:02 PM, Allen Wirfs-Brock wrote: ... 1) stratification - Proxy.isProxy is an example 2) It is impossible or inconvenient to add the classification interface to the appropriate instance interface. I think that was the reason for Array.isArray. We were more afraid of adding it to Array.prototype than to Array. That's too bad, since we added Function.prototype.isGenerator (which as I noted last time shows up on Function, of course!) without problem. It's Object.prototype that people can't extend, and then the warning about other built-ins is pre-ES5, out of lack of enumerable control, so for fear of breaking for-in loops. Adding a non-enumerable Array.prototype method seems doable to me, if the name is clear and not commonly used. We can probably still add Array.prototoype.isArray if that would help to establish the pattern. Document as being preferred over Array.isArray Well, if you were actually using Proxy to implement Array, you would probably also be providing the Array implementation. Not necessarily -- you might just want isArray to return true for your proxied fakes and the real deal. But yes, you would have to provide at least Array.isArray. With all the standard methods on Array.prototype generic, I believe you wouldn't have to replace all of Array. but if you want (new Array()) to create the fakes... However, arguably Array.isArray really should have been Array.prototype.isArray. We treated as a case 2 from above. May we really didn't need to, but that's water over dam. I don't think we should use it as precedent for more greenfield situations. First thought: Crock sold us on reformed Number.is{NaN,Finite} along with new Numer.isInteger. We can do both and correct course for the long run, perhaps: Array.isArray and Array.prototype.isArray. But first we should settle the data property vs. method issue. yes. I wonder if isArray and isInteger are different kinds of categorizations (class based vs value based) that perhaps should have distinct naming conventions. But f the method implementation is simply going to be return true or return false why do yo need to call it at all? The general case is an optional method, a notification hook call-out or some such. You're right that for object detection the method call seems a waste. We could have made Function isGenerator a non-writable, non-configurable, and non-enumerable boolean-valued data property. But, then it would be an instance property, which burns storage (the shape or hidden class sharing is not the issue, the true or false value slot is); or else an accessor on Function.prototype, which is about as costly as a method in implementations. Users testing it would not have to write (), your point. That wins. But otherwise it's a wash. Why not a non-writable,non-enumerable non-configurable data property on Function.prototype. Smalltalk code guidelines would general say (but I'll use JS syntax here instead of Smalltalk): obj.isFoo//ok obj.constructor.isFoo(obj) //less ok, unless there is a good reason Foo.isFoo(obj) //even less good In the first two you are only statically coupled to obj in the third you are statically coupled to both obj and Foo. Right, and as you say there may be a reason for that (reasons 1 and 3; not sure about 2). and I should have included: obj.constructor === Foo //very bad Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 10, 2011, at 10:54 AM, Allen Wirfs-Brock wrote: However, arguably Array.isArray really should have been Array.prototype.isArray. We treated as a case 2 from above. May we really didn't need to, but that's water over dam. I don't think we should use it as precedent for more greenfield situations. First thought: Crock sold us on reformed Number.is{NaN,Finite} along with new Numer.isInteger. We can do both and correct course for the long run, perhaps: Array.isArray and Array.prototype.isArray. But first we should settle the data property vs. method issue. yes. I wonder if isArray and isInteger are different kinds of categorizations (class based vs value based) that perhaps should have distinct naming conventions. The isFoo predicate convention is used variously in other languages. Kind of like foop in Lisps. I would not tax name length just to distinguish these cases, or others (ontology fanatics will soon be splitting sub-cases of value-based). But f the method implementation is simply going to be return true or return false why do yo need to call it at all? The general case is an optional method, a notification hook call-out or some such. You're right that for object detection the method call seems a waste. We could have made Function isGenerator a non-writable, non-configurable, and non-enumerable boolean-valued data property. But, then it would be an instance property, which burns storage (the shape or hidden class sharing is not the issue, the true or false value slot is); or else an accessor on Function.prototype, which is about as costly as a method in implementations. Users testing it would not have to write (), your point. That wins. But otherwise it's a wash. Why not a non-writable,non-enumerable non-configurable data property on Function.prototype. We're talking about isGenerator, right? There is no Generator constructor, no shadowing opportunity. Function can create a generator if yield occurs in the body string. Here's a reason to prefer a method, at least one method per feature: you can then object-detect the method without having to autoconf-style try/catch a generator in eval or Function. Just if (Function.prototype.isGenerator) or (shorter, works as noted) if (Function.isGenerator) to guard code (perhaps a script injection) that depends on generator support. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On 9 July 2011 00:24, Brendan Eich bren...@mozilla.com wrote: On Jul 8, 2011, at 2:43 PM, Andreas Rossberg wrote: One minor suggestion I'd have is to treat names as a proper new primitive type, i.e. typeof key == name, not object. That way, it can be defined much more cleanly what a name is, where its use is legal (as opposed to proper objects), and where it maybe enjoys special treatment. We went back and forth on this. I believe the rationale is in the wiki (but perhaps in one of the strawman:*name* pages). There are a couple of reasons: 1. We want private name objects to be usable as keys in WeakMaps. Clearly we could extend WeakMaps to have either object (but not null) or name typeof-type keys, but that complexity is not warranted yet. I can see that being a relevant use case for weak maps. But the same logic applies to using, say, strings or numbers as keys. So isn't the right fix rather to allow weak maps to be keyed on any JS value? 2. Private name objects are deeply frozen and behave like value types (since they have no copy semantics and you can only generate fresh ones). Thus they are typeof-type object but clearly distinct from string-equated property names that JS has sported so far. Oh, of course you meant to distinguish private names via typeof precisely to tell that they are not converted to strings when used as property names. For that test, the proposal http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects proposes an isName predicate function exported from the @name built-in module. Yes, I know. But this is introducing a kind of ad-hoc shadow type mechanism. Morally, this is a type distinction, so why not make it one? Moreover, I feel that ES already has too many classification mechanisms (typeof, class, instanceof), so adding yet another one through the back door doesn't seem optimal. [...] in the case of private name objects, we don't think we have good enough reason to add a typeof name -- and then to complicate WeakMap. Why do you think that it would that make WeakMap more complicated? As far as I can see, implementations will internally make that very type distinction anyways. And the spec also has to make it, one way or the other. I like this notation most, because it can be generalised in a consistent manner beyond the special case of private names: there is no reason that the bit in brackets is just an identifier, we could allow arbitrary expressions. Then the shape of the object is not static. Perhaps this is worth the costs to implementations and other analyzers (static program analysis, human readers). We should discuss a bit more first, as I just wrote in reply to Allen. I don't think that the more general form would be a big deal for implementations. And it is still easy to identify object expressions with static shape syntactically: they don't use [_]. Analyses shouldn't be harder than for a series of assignments (which you probably could desugar this into). Here, with obj = { [expr]: value } as the way to compute a property name in an object initialiser (I must not write object literal any longer), we are proceeding up another small and separate hill. But, is this the right design for object initialisers (which the normative grammar does call ObjectLiterals)? (If you care about that, then that's a misnomer already, since the property values have always been arbitrary expressions.) Thanks, /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 9, 2011, at 9:45 AM, Brendan Eich wrote: On Jul 9, 2011, at 8:48 AM, Brendan Eich wrote: See above, there is nothing novel or evil in isName or isArray (another example) isGenerator. Also the Proxy.isTrapping, which in recent threads has been proposed to be renamed to Proxy.isProxy or Object.isProxy. This is a fine point, but we may as well hash it out, and es-discuss is the best place: do we want Object.isFoo predicates, in spite of precedent such as ES5's Array.isArray (which I've whined about as redundantly and a bit misleadingly named, since its argument can be any value -- Object seems a better home, lacking a Value class built-in constructor in which to bind Value.isArray). A consideration here is that whatever conventions we follow sets a precedent for user written code. I don't think we want to encourage the addition of such classification functions to Object or Object.prototype. So from that perspective Array.isArray, Function.isGenerator, and Proxy.isProxy are better exemplars than Object.isArray, etc. Another consider is whether such classification schemes should use methods or data properties. An instance side, isFoo() method to be useful generally will also require the addition of an isFoo() method to Object.prototype while a instance side-data property can be tested without augmenting Object.prototype (({}.isFoo) is a falsey value). Also most user code classification schemes will require some sort of instance-side tagging or testing to actually determine whether the instance is in the category. For those reasons, I think the best classification scheme is usually via an instance-side public data property with a truthy value. But there are some exceptions. For the legacy built-ins such as Array is less risky from compatibility perspective to add properties to the constructor than to the prototype. However, for new built-in this isn't an issue. Another consider is meta layering: meta-layer functions shouldn't be exposed on application layer objects. Proxy.isProxy is a good example. Proxy instances operate as application objects in the application layer. The public face of application objects shouldn't be polluted with a meta-layer method such as isProxy. Back at the dawn of time, isNaN and isFinite as value predicates. Not relevant, since not type predicates, but worth a mention. ES.next adds saner (no argument coercion) Number.is{NaN,Finite} predicates, and Number.isInteger for testing whether a number is an integral IEEE 754 double. For ES.next, Function.prototype.isGenerator has been proposed (and implemented in Firefox 4 and up). Why not Function.isGenerator? Because the prototype method is more usable when you have a function in hand and you want to know whether it's a generator. If you have any value x, you'll need (typeof x === 'object' 'isGenerator' in x) guarding the x.isGenerator() call, but we judged that as the less common use-case. Why not just make it a boolean-valued data property? Could be we were wrong, or that being consistent even in the hobgoblin/foolish sense is better. But Object.isGenerator crowds poor Object more. Notice how there's no need for Function.isFunction or Object.isFunction, due to typeof (function(){}) === 'function'. That's another dawn-of-time distinction that does not fit the primitiive types vs. object classification use-case I mentioned in the last message -- testing whether x is callable via typeof x === 'function', at least for native if not host objects, is the use-case here. But this is a special case that does not extrapolate to other situations. Some of these questions are at this point more about aesthetics and Principles than about usability. And we have historic messiness in how things have grown. In a utilitarian language like JS, usability and aesthetic considerations should be closely linked and consistency is a significant usability factor. . Principles are a way to get consistent application of such aesthetics . Given Array.isArray, perhaps we are better off with Proxy.isProxy. The case for Function.prototype.isGenerator hangs on usability, so I'd like to let the prototype in Firefox ride for a bit. If someone wants to propose a better, extensible and uniform system of type predicate naming, please do. a start of one is above. The exceptions and special cases are where good judgement is required. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 9, 2011, at 11:19 AM, Allen Wirfs-Brock wrote: A consideration here is that whatever conventions we follow sets a precedent for user written code. I don't think we want to encourage the addition of such classification functions to Object or Object.prototype. So from that perspective Array.isArray, Function.isGenerator, and Proxy.isProxy are better exemplars than Object.isArray, etc. Do we want users extending Array or Function? Same problem as applies to object. In Edition N+1 we then collide. I don't see a difference in kind here. Another consider is whether such classification schemes should use methods or data properties. An instance side, isFoo() method to be useful generally will also require the addition of an isFoo() method to Object.prototype while a instance side-data property can be tested without augmenting Object.prototype (({}.isFoo) is a falsey value). Also most user code classification schemes will require some sort of instance-side tagging or testing to actually determine whether the instance is in the category. For those reasons, I think the best classification scheme is usually via an instance-side public data property with a truthy value. I don't agree, because detecting truthy valued properties from host objects (proxies) can have effects. Calling a well-known predicate function that has no side effects is better. But there are some exceptions. For the legacy built-ins such as Array is less risky from compatibility perspective to add properties to the constructor than to the prototype. However, for new built-in this isn't an issue. Another consider is meta layering: meta-layer functions shouldn't be exposed on application layer objects. Proxy.isProxy is a good example. Proxy instances operate as application objects in the application layer. The public face of application objects shouldn't be polluted with a meta-layer method such as isProxy. Do we want intercession by proxies for any of these isFoo predicates? Why not Function.isGenerator? Because the prototype method is more usable when you have a function in hand and you want to know whether it's a generator. If you have any value x, you'll need (typeof x === 'object' 'isGenerator' in x) guarding the x.isGenerator() call, but we judged that as the less common use-case. Why not just make it a boolean-valued data property? See above, and also isArray and other isFoo predicates (isNaN) realized via functions, not data properties. Could be we were wrong, or that being consistent even in the hobgoblin/foolish sense is better. But Object.isGenerator crowds poor Object more. Notice how there's no need for Function.isFunction or Object.isFunction, due to typeof (function(){}) === 'function'. That's another dawn-of-time distinction that does not fit the primitiive types vs. object classification use-case I mentioned in the last message -- testing whether x is callable via typeof x === 'function', at least for native if not host objects, is the use-case here. But this is a special case that does not extrapolate to other situations. Agreed, although we've said we'll consider typeof-result extensions in the future, e.g., for user-defined value types. Some of these questions are at this point more about aesthetics and Principles than about usability. And we have historic messiness in how things have grown. In a utilitarian language like JS, usability and aesthetic considerations should be closely linked and consistency is a significant usability factor. . Principles are a way to get consistent application of such aesthetics . It's hard to disagree, but that doesn't settle disagreements, e.g., on data properties that are true-valued or missing, so undefined-falsy, vs. pure predicate functions. In the worst case, our argument now must go meta. I don't want to do that. My argument for pure predicates is concrete, not abstract. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 9, 2011, at 5:02 PM, Brendan Eich wrote: On Jul 9, 2011, at 4:32 PM, Allen Wirfs-Brock wrote: On Jul 9, 2011, at 1:43 PM, Brendan Eich wrote: ... However, if that isn't your concern (and my perspective is that in most cases it shouldn't be) you might as well use a public trademark property on instances in which case a direct test for that property is probably all you need. However, I agree that best practice would be for such property to have no access side-effects, regardless of how they were are implemented. I'm not sure which however wins. ;-) We have Array.isArray, Proxy.isTrapping (soon to be Proxy.isProxy?), Function.prototype.isGenerator (could be Function.isGenerator -- note different signature, so don't want both -- Object.getPrototypeOf(Function) === Function.prototype). I think methods on built-ins are winning. I think instances that are self identifying or self classify are best and should be the default approach. Whether via a method or data properties is a secondary issue. However, some times you need to use have a separate object (let's call it an oracle) that is responsibility for doing the identification/classification. There are various reasons for using an oracle: 1) stratification - Proxy.isProxy is an example 2) It is impossible or inconvenient to add the classification interface to the appropriate instance interface. I think that was the reason for Array.isArray. We were more afraid of adding it to Array.prototype than to Array. 3) Externally imposed classification that is not cohesive with the rest of an object's behavior. For example, isSerializable is probably dependent upon a specific kind of serializer. Their might even be multiple different kinds of serializers installed in a system. In general yes. Let's say a new kind of Crazy DOM node interfaces defines that it has a isCrazyNode property and also has semantics that requires the use of a Proxy to natively implement it in JS. In that case isCrazyNode needs to be implemented via the Proxy. On the other hand, we don't want random objects to start exposing isProxy properties just because a Proxy was used to implement it. That would be unnecessarily exposing implementation details. I fall back o my standard test, can Proxies be used to implement ES Array instances. If such instances implemented via Proxies had a visible isProxy property they would not be a accurate implemented on the ES spec. for array instances. Right, so Proxy.isProxy -- on the built-in namespace object (like a standard module binding), not on each proxy instance. But if you want isCrazyNode intercession via a Proxy, as you state above. For an Array emulation via Proxy, you'd have to monkey-patch Array.isArray. Well, if you were actually using Proxy to implement Array, you would probably also be providing the Array implementation. However, arguably Array.isArray really should have been Array.prototype.isArray. We treated as a case 2 from above. May we really didn't need to, but that's water over dam. I don't think we should use it as precedent for more greenfield situations. My argument for self-identify objects rather than independent predicates is above. A brand or trademark is quite different from some old truthy data property. One could use private name objects for branding, no forgeries possible. Yes, but I think the situation where you really need a non-forgage brand are relative rare compared to the the need to do more basic classificaiton. If you buy that self-identify is better, then the question becomes data property or instance methods. To me that comes down to, if someone is going to have to code if (typeof obj.isFoo == 'function' obj.isFoo()) ... why not just say if (obj.isFoo)... I agree with that style when detecting a method. BTW, it leads to the obj.isFoo?() conditional call idea (along with ?.). We never reached consensus there. But f the method implementation is simply going to be return true or return false why do yo need to call it at all? A major source of monkey patch in large Smalltalk applications was people adding isFoo ^false methods to Object so they could test arbitrary objects for Fooness. I'm assume that as people start building complex JS applications using rich inheritance based duck typed class libraries they are going to find the need to do the same sort of classification testing. This was instance-side, right? Seems like it would work with class-side inheritance too. In Smalltalk, yes, on the instance side. But you can always reach the class side from the instance. Essentially the equivalent of JS obj.constructor. Smalltalk coe guidelines would general say (but I'll use JS syntax here instead of Smalltalk): obj.isFoo//ok obj.constructor.isFoo(obj) //less ok, unless there is a good reason Foo.isFoo(obj) //even less good In the first two you are only
Re: using Private name objects for declarative property definition.
On Jul 9, 2011, at 7:22 PM, Allen Wirfs-Brock wrote: On Jul 9, 2011, at 5:02 PM, Brendan Eich wrote: On Jul 9, 2011, at 4:32 PM, Allen Wirfs-Brock wrote: On Jul 9, 2011, at 1:43 PM, Brendan Eich wrote: ... However, if that isn't your concern (and my perspective is that in most cases it shouldn't be) you might as well use a public trademark property on instances in which case a direct test for that property is probably all you need. However, I agree that best practice would be for such property to have no access side-effects, regardless of how they were are implemented. I'm not sure which however wins. ;-) We have Array.isArray, Proxy.isTrapping (soon to be Proxy.isProxy?), Function.prototype.isGenerator (could be Function.isGenerator -- note different signature, so don't want both -- Object.getPrototypeOf(Function) === Function.prototype). I think methods on built-ins are winning. I think instances that are self identifying or self classify are best and should be the default approach. Whether via a method or data properties is a secondary issue. However, some times you need to use have a separate object (let's call it an oracle) that is responsibility for doing the identification/classification. There are various reasons for using an oracle: 1) stratification - Proxy.isProxy is an example 2) It is impossible or inconvenient to add the classification interface to the appropriate instance interface. I think that was the reason for Array.isArray. We were more afraid of adding it to Array.prototype than to Array. That's too bad, since we added Function.prototype.isGenerator (which as I noted last time shows up on Function, of course!) without problem. It's Object.prototype that people can't extend, and then the warning about other built-ins is pre-ES5, out of lack of enumerable control, so for fear of breaking for-in loops. Adding a non-enumerable Array.prototype method seems doable to me, if the name is clear and not commonly used. 3) Externally imposed classification that is not cohesive with the rest of an object's behavior. For example, isSerializable is probably dependent upon a specific kind of serializer. Their might even be multiple different kinds of serializers installed in a system. Good point. In general yes. Let's say a new kind of Crazy DOM node interfaces defines that it has a isCrazyNode property and also has semantics that requires the use of a Proxy to natively implement it in JS. In that case isCrazyNode needs to be implemented via the Proxy. On the other hand, we don't want random objects to start exposing isProxy properties just because a Proxy was used to implement it. That would be unnecessarily exposing implementation details. I fall back o my standard test, can Proxies be used to implement ES Array instances. If such instances implemented via Proxies had a visible isProxy property they would not be a accurate implemented on the ES spec. for array instances. Right, so Proxy.isProxy -- on the built-in namespace object (like a standard module binding), not on each proxy instance. But if you want isCrazyNode intercession via a Proxy, as you state above. For an Array emulation via Proxy, you'd have to monkey-patch Array.isArray. Well, if you were actually using Proxy to implement Array, you would probably also be providing the Array implementation. Not necessarily -- you might just want isArray to return true for your proxied fakes and the real deal. But yes, you would have to provide at least Array.isArray. With all the standard methods on Array.prototype generic, I believe you wouldn't have to replace all of Array. However, arguably Array.isArray really should have been Array.prototype.isArray. We treated as a case 2 from above. May we really didn't need to, but that's water over dam. I don't think we should use it as precedent for more greenfield situations. First thought: Crock sold us on reformed Number.is{NaN,Finite} along with new Numer.isInteger. We can do both and correct course for the long run, perhaps: Array.isArray and Array.prototype.isArray. But first we should settle the data property vs. method issue. If you buy that self-identify is better, then the question becomes data property or instance methods. To me that comes down to, if someone is going to have to code if (typeof obj.isFoo == 'function' obj.isFoo()) ... why not just say if (obj.isFoo)... I agree with that style when detecting a method. BTW, it leads to the obj.isFoo?() conditional call idea (along with ?.). We never reached consensus there. But f the method implementation is simply going to be return true or return false why do yo need to call it at all? The general case is an optional method, a notification hook call-out or some such. You're right that for object detection the method call seems a waste. We could have made Function isGenerator a
using Private name objects for declarative property definition.
The current Harmony classes proposal http://wiki.ecmascript.org/doku.php?id=harmony:classes includes the concept of private instance members and syntax for defining them. While it presents a syntax for accessing them (eg, private(foo).bar accesses the private 'bar' member of the object that is the value of foo) there does not yet appear to be consensus acceptance of this access syntax. There also appears to be a number of still unresolved semantic issues WRT such private instance members. As an alternative, the classes proposal says that a pattern composing private names with classes can satisfy the requirements for private instance members. However, so far, we have no concrete proposals on what these patterns might be. This is a first cut at proposing such patterns. The current versions of the private names proposal http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects simply exposes a constructor for creating unique values can be be used as property keys: const key = Name.create(); Those values can be used to define and access object properties: let obj = {}; print(obj.key); // undefined obj[key]=some private state; //create a property with a private name key print(obj.key);// still undefined print(obj[key]); //undefined print(obj[key]); // some private state In the above example the property created by using the private name value is essentially an instance private member of obj. In general, various levels of visibility (instance private, class private, friends, module private, etc.) can be achieved simply by using lexical scoping to control access to the variable that contain private key values. For example: function ObjWithInstancePrivateMember(secret) { const s = Name.create(); this[s]= secret; this.method = function () { return doSomething(this[s]); } } const priv = Name.create(); function ObjWithClassPrivateMember(secret) { const s = Name.create(); this[priv]= secret; this.method = function (another) { return doSomethingTogether(this[priv], another[priv]); } } Note that [ ] notation with an expression that evaluates to a private name value must be used to access such private members. There have been various proposals such as http://wiki.ecmascript.org/doku.php?id=strawman:names and http://wiki.ecmascript.org/doku.php?id=strawman:private_names to allow dotted property access to be used with private name keys. However, these either had technical problems or their semantics were rejected by community discussion on this list. The issues raised in response to those proposals are likely to recur for any private member access syntax that uses dot notation. A weakness with using private names object to define private members is that currently there is no way to declaratively define objects with such members. Consider the following definition of a Point abstraction using extended object literals and a simple naming convention to identify private members: const Point = { //private members __x: 0, __y: 0, __validate(x,y) { return typeof x == 'number' typeof y = 'number'}, //public members new(x,y) { if (!this.__validate(x,y)) throw invalid; return this | { __x: x, __y: y } }; add(anotherPoint) { return this.new(this.__x+another.__x, this.__y+another.__y) } } using private name objects this would have to be written as: const __x=Name.create(); const __y=Name.create(); const __validate=Name.create(); const Point = { //public members new(x,y) { if (!this[__validate](x,y)) throw invalid; let obj = this | { }; obj[__x] = x; obj[__y] = y; return obj; }; add(anotherPoint) { return this.new(this[__x]+another[__x], this[__y]+another[__y]) } } //private members for Point Point[__x] = 0; Point[__y] = 0,; Point[ __validate] = function (x,y) { return typeof x == 'number' tyupeof y = 'number'}; The biggest issue with this rewrite is it loose the declarative definition and replaces it with an hybrid that has some declarative parts and some imperative parts. This breaks the logical encapsulation of the definition of the Point abstraction making it harder to be understand and manipulated by both humans and mechanical tools. The reason this is necessary is that object literal notation currently has no way to express that a definition of a property uses a name that needs be interpreted as a reference to a lexical variable whose value is the actual private name object. Fixes these requires an additional extension to object literals. One possibility, is to use a prefix keyword to each property definition that uses a private key value. For example: const __x=Name.create(); const
Re: using Private name objects for declarative property definition.
On 8 July 2011 21:16, Allen Wirfs-Brock al...@wirfs-brock.com wrote: The current versions of the private names proposal http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects simply exposes a constructor for creating unique values can be be used as property keys: Of the several private names proposals around, I find this one preferable. It is clean and simple, and provides the functionality needed in an orthogonal manner. It seems worth exploring how well this works in practice before we settle on something more complicated. One minor suggestion I'd have is to treat names as a proper new primitive type, i.e. typeof key == name, not object. That way, it can be defined much more cleanly what a name is, where its use is legal (as opposed to proper objects), and where it maybe enjoys special treatment. Another alternative that avoids using the 'private' prefix is to allow the property name in a property definition to be enclosed with brackets: const __x=Name.create(); const __y=Name.create(); const __validate=Name.create(); Point = { //private members [__x]: 0, [ __y]: 0, [__validate](x,y) { return typeof x == 'number' typeof y = 'number'}, //public members new(x,y) { if (!this[__validate](x,y)) throw invalid; return this | { [__x]: x, [__y]: y } }; add(anotherPoint) { return this.new(this[__x]+another[__x], this[__y]+another[__y]) } } I like this notation most, because it can be generalised in a consistent manner beyond the special case of private names: there is no reason that the bit in brackets is just an identifier, we could allow arbitrary expressions. So the notation would be the proper dual to bracket access notation. From a symmetry and expressiveness perspective, this is very appealing. Notation-wise, I think people would get used to using brackets. I see no good reason to introduce yet another projection syntax, like @. Whether additional sugar is worthwhile -- e.g. private declarations -- remains to be explored. (To be honest, I haven't quite understood yet in what sense such sugar would really be more declarative. Sure, it is convenient and perhaps more readable. But being declarative is a semantic property, and cannot be achieved by simple syntax tweaks.) /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 8, 2011, at 12:16 PM, Allen Wirfs-Brock wrote: The current Harmony classes proposal http://wiki.ecmascript.org/doku.php?id=harmony:classes includes the concept of private instance members and syntax for defining them. While it presents a syntax for accessing them (eg, private(foo).bar accesses the private 'bar' member of the object that is the value of foo) there does not yet appear to be consensus acceptance of this access syntax. Oh, quite the opposite -- everyone on TC39 with whom I've spoken agrees that private(this) syntax is straw that must be burned up. We need new syntax, or else we need to do at least what Dave proposed in minimal classes: defer private syntax, let programmers use private name objects and explicit [] indexing. I do think we can say a few more things about private in class that may not be in the requirements on the wiki: On the level of rationale for why class- and not instance-private, Juan Ignacio Dopazo in private correspondence made a good observation, shown by his example: class MyClass { private foo() {} bar() { var self = this; setTimeout(function () { self.foo(); }, 0); } } Because |this| is not lexical, instance- rather than class-private access that requires this. to the left of the private variable reference does not work unless you use the closure pattern explicitly and abjure |this|. The straw private(foo) syntax doesn't help if the goal is instance privacy, since the inner function has no idea (runtime, never mind compile-time) how to enforce to which particular instance self must refer. Requiring .bind(this) after the function expression passed to setTimeout can be used to work around such a hypothetical, mandatory this.-prefix instance-private restriction, but that's onerous and it can be too costly. Another observation about any class-private scheme we might consider in the current context: private instance variables in many ways (notably not for Object.freeze) act like properties, and the syntax mooted so far casts them in that light. Even if the ES5 reflective APIs, such as Object.getOwnPropertyNames, rightly skip privates on a class instance, proxies may raise the question: how does a private variable name reflect as a property name? class Point { constructor(x, y) { private x = x, y = y; } equals(other) { return private(this).x is private(other).x private(this).y is private(other).y; } ... } We cannot know how to ask for private-x from other without special syntax of some kind, either at the access point or as a binding declaration affecting all .x (and x: in object literals). So here I use the proposal's straw private(foo) syntax. Could other be a proxy that somehow has a private data record? Could other denote a class instance whose [[Prototype]] is a proxy? I claim we do not want private(foo) by itself, no .x after, to reify as an object, but if it did, then it seems to me it could be used with a proxy to reflect on private variable names. There are certainly several choices here, but the one I currently favor as simplest, which creates no new, ad-hoc concepts in the language, is that class-private instance variables are properties named by private name objects. Per the requirements for private in the classes proposal, this means Object.freeze does not freeze private-named properties, or at least does not make private-named data properties non-writable. Perhaps Object.preventExtensions should not restrict private-named properties from being added to the object. This seems strange and wrong at first, but if freeze does not affect private-named properties, I'm not sure there is a defensive consistency threat from attackers decorating frozen objects with ad-hoc properties named by private name objects that only the attacker could create or access, which cannot collide with the class's private names. Perhaps this is uncontroversial. I hope so, but I don't assume it is, and I suspect people who worked on the classes proposal may disagree. Cc'ing Mark in particular, since I'm just writing down some preliminary thoughts and intermediate conclusions here, and I could be way off base. Ok, back to your good point about wanting private names to be usable in object initialisers: Each of these approaches seem plausible. For a side-by-side comparison of the above example using the alternatives see http://wiki.ecmascript.org/lib/exe/fetch.php?id=harmony%3Aprivate_name_objectscache=cachemedia=harmony:private-name-alternatives.pdf . I'm interested in feedback on the alternatives. The first thing I'd say is that, whatever the syntax (private prefix, [] around property name, or @ prefix), it seems too verbose to require boilerplate of const __x = Name.create(), etc. before the object literal. Wouldn't it be better for the prefix, brackets or sigil to by itself define a private name and use it, also putting it in scope for the
Re: using Private name objects for declarative property definition.
On Jul 8, 2011, at 2:43 PM, Andreas Rossberg wrote: One minor suggestion I'd have is to treat names as a proper new primitive type, i.e. typeof key == name, not object. That way, it can be defined much more cleanly what a name is, where its use is legal (as opposed to proper objects), and where it maybe enjoys special treatment. We went back and forth on this. I believe the rationale is in the wiki (but perhaps in one of the strawman:*name* pages). There are a couple of reasons: 1. We want private name objects to be usable as keys in WeakMaps. Clearly we could extend WeakMaps to have either object (but not null) or name typeof-type keys, but that complexity is not warranted yet. 2. Private name objects are deeply frozen and behave like value types (since they have no copy semantics and you can only generate fresh ones). Thus they are typeof-type object but clearly distinct from string-equated property names that JS has sported so far. In other words, we don't gain any distinctiveness, or make any particular claims about private name objects that could not be made about other (deeply-frozen, generated-only, say by Object.create or Proxy.create in a distinguished factory function) kinds of objects, via a new typeof-type. In light of these points, making private names be objects in both the as-proposed WeakMap sense, and the typeof-result sense, seems best. So, while we have added null as a new typeof result string, and we have considered adding other novel typeof results, and we believe that we can add new typeof results with good enough reason (thanks to IE adding some non-standard ones long ago, and programmers tending to write non-exhaustive switch and if-else cond structures to handle only certain well-known typeof cases), in the case of private name objects, we don't think we have good enough reason to add a typeof name -- and then to complicate WeakMap. Point = { //private members [__x]: 0, [ __y]: 0, [__validate](x,y) { return typeof x == 'number' typeof y = 'number'}, //public members new(x,y) { if (!this[__validate](x,y)) throw invalid; return this | { [__x]: x, [__y]: y } }; add(anotherPoint) { return this.new(this[__x]+another[__x], this[__y]+another[__y]) } } I like this notation most, because it can be generalised in a consistent manner beyond the special case of private names: there is no reason that the bit in brackets is just an identifier, we could allow arbitrary expressions. Then the shape of the object is not static. Perhaps this is worth the costs to implementations and other analyzers (static program analysis, human readers). We should discuss a bit more first, as I just wrote in reply to Allen. So the notation would be the proper dual to bracket access notation. From a symmetry and expressiveness perspective, this is very appealing. It does help avoid eval abusage, on the upside. Proceeding bottom-up, with orthogonal gap-filling primitives that compose well, is our preferred way for Harmony. Private name objects without new syntax, requiring bracket-indexing, won in part by filling a gap without jumping to premature syntax with novel binding semantics (see below). Here, with obj = { [expr]: value } as the way to compute a property name in an object initialiser (I must not write object literal any longer), we are proceeding up another small and separate hill. But, is this the right design for object initialisers (which the normative grammar does call ObjectLiterals)? Notation-wise, I think people would get used to using brackets. I see no good reason to introduce yet another projection syntax, like @. Agreed that unless we use @ well for both property names in initialisers and private property access, and in particular if we stick with bracketing for access, then square brackets win for property naming too -- but there's still the loss-of-static-shape issue. Whether additional sugar is worthwhile -- e.g. private declarations -- remains to be explored. (To be honest, I haven't quite understood yet in what sense such sugar would really be more declarative. Sure, it is convenient and perhaps more readable. But being declarative is a semantic property, and cannot be achieved by simple syntax tweaks.) Good point! The original name declaration via private x that Dave championed was definitely semantic: it created a new static lookup hierarchy, lexical but for names after . in expressions and before : in property assignments in object literals. This was, as Allen noted, controversial and enough respected folks on es-discuss (I recall Andrew Dupont in particular) and in TC39 reacted negatively that we separated and deferred it. I do not know how to revive it productively. /be___ es-discuss mailing list es-discuss@mozilla.org
Re: using Private name objects for declarative property definition.
On Jul 8, 2011, at 3:24 PM, Brendan Eich wrote: On Jul 8, 2011, at 2:43 PM, Andreas Rossberg wrote: Point = { //private members [__x]: 0, [ __y]: 0, [__validate](x,y) { return typeof x == 'number' typeof y = 'number'}, //public members new(x,y) { if (!this[__validate](x,y)) throw invalid; return this | { [__x]: x, [__y]: y } }; add(anotherPoint) { return this.new(this[__x]+another[__x], this[__y]+another[__y]) } } I like this notation most, because it can be generalised in a consistent manner beyond the special case of private names: there is no reason that the bit in brackets is just an identifier, we could allow arbitrary expressions. Then the shape of the object is not static. Perhaps this is worth the costs to implementations and other analyzers (static program analysis, human readers). We should discuss a bit more first, as I just wrote in reply to Allen. This is one of the reason I think I slightly prefer the @ approach. @ can be defined similarly to . in that it must be followed by an identifier and that the identifier must evaluate to a private name object. The latter in the general case would have to be a runtime check but in many common cases could be statically verified. Even this doesn't guarantee that we statically know the shape. Consider: function f(a,b) { return { @a: 1, @b: 2 } }; const x=Name.create(); const y=Name.create(); let obj1 = f(x,y); let obj2 = f(y,x); let obj3 = f(x,x); let obj4 = f(y,y); So the notation would be the proper dual to bracket access notation. From a symmetry and expressiveness perspective, this is very appealing. It does help avoid eval abusage, on the upside. If you mean something like: let propName = computeSomePropertyName(); let obj = eval(({+propName+: null})); I'd say whoever does that doesn't know the language well enough as their are already good alternatives such as: let obj = {}; obj[propName] = null; or let obj = new Object; Object.defineProperty(obj, propName,{value:null, writable: true, enumerable: true, configurable: true}); I know that people do stupid and unnecessary eval tricks today but I doubt that adding yet another non-eval way to accomplish the same thing is going to stop that. The basic problem they have is not knowing the language and make the language bigger isn't likely to make things any better for them. Proceeding bottom-up, with orthogonal gap-filling primitives that compose well, is our preferred way for Harmony. Private name objects without new syntax, requiring bracket-indexing, won in part by filling a gap without jumping to premature syntax with novel binding semantics (see below). Here, with obj = { [expr]: value } as the way to compute a property name in an object initialiser (I must not write object literal any longer), we are proceeding up another small and separate hill. But, is this the right design for object initialisers (which the normative grammar does call ObjectLiterals)? Another concern is that it creates another look-ahead issue for the unify blocks and object initializers proposal. Notation-wise, I think people would get used to using brackets. I see no good reason to introduce yet another projection syntax, like @. Agreed that unless we use @ well for both property names in initialisers and private property access, and in particular if we stick with bracketing for access, then square brackets win for property naming too -- but there's still the loss-of-static-shape issue. There is also an argument to me made that [ ] and @ represent two different use cases: computed property access and private property access and for that reason there should be a syntactic distinction between them. Whether additional sugar is worthwhile -- e.g. private declarations -- remains to be explored. (To be honest, I haven't quite understood yet in what sense such sugar would really be more declarative. Sure, it is convenient and perhaps more readable. But being declarative is a semantic property, and cannot be achieved by simple syntax tweaks.) Good point! The original name declaration via private x that Dave championed was definitely semantic: it created a new static lookup hierarchy, lexical but for names after . in expressions and before : in property assignments in object literals. This was, as Allen noted, controversial and enough respected folks on es-discuss (I recall Andrew Dupont in particular) and in TC39 reacted negatively that we separated and deferred it. I do not know how to revive it productively. Actually, I think you can blame be rather than Dave for the dual lookup hierarchy. Dave (and Sam's) original proposal did have special semantics for private name declarations bug only had a single lookup
Re: using Private name objects for declarative property definition.
On Jul 8, 2011, at 4:21 PM, Allen Wirfs-Brock wrote: On Jul 8, 2011, at 3:24 PM, Brendan Eich wrote: Then the shape of the object is not static. Perhaps this is worth the costs to implementations and other analyzers (static program analysis, human readers). We should discuss a bit more first, as I just wrote in reply to Allen. This is one of the reason I think I slightly prefer the @ approach. @ can be defined similarly to . in that it must be followed by an identifier and that the identifier must evaluate to a private name object. The latter in the general case would have to be a runtime check but in many common cases could be statically verified. This does give a probabilistic edge to implementations optimizing @ for private names only (but with guards that throw on non-private-name result of evaluating what's on the right of @), and predicting or static-analyzing shape. Even this doesn't guarantee that we statically know the shape. Certainly not. And that is a change from today, which I think we ought to discuss. Because as you note below, private names could be added to objects declared by initialisers that lack any private name syntax, after the initialiser; or after a new Object or Object.create. It does help avoid eval abusage, on the upside. If you mean something like: let propName = computeSomePropertyName(); let obj = eval(({+propName+: null})); I'd say whoever does that doesn't know the language well enough as their are already good alternatives such as: let obj = {}; obj[propName] = null; or let obj = new Object; Object.defineProperty(obj, propName,{value:null, writable: true, enumerable: true, configurable: true}); No, the problem is not that there aren't other ways to do this. Or that people are too dumb. I've had real JS users, often with some Python knowledge but sometimes just proceeding from a misunderstanding of how object literals are evaluated, ask for the ability to compute property names selectively in object literals. Sure, they can add them after. That sauce for the goose is good for the private-name gander too, no? Here, with obj = { [expr]: value } as the way to compute a property name in an object initialiser (I must not write object literal any longer), we are proceeding up another small and separate hill. But, is this the right design for object initialisers (which the normative grammar does call ObjectLiterals)? Another concern is that it creates another look-ahead issue for the unify blocks and object initializers proposal. Yes, indeed. I've been thinking about that one since yesterday, without much more to show for it except this: I think we should be careful not to extend object literals in ways that create more ambiguity with blocks. I grant that the new method syntax is too sweet to give up. But I don't think {[..., {!..., etc. for # and ~ after {, are yet worth the ambiguity that is future-hostile to block vs. object literal unity. Agreed that unless we use @ well for both property names in initialisers and private property access, and in particular if we stick with bracketing for access, then square brackets win for property naming too -- but there's still the loss-of-static-shape issue. There is also an argument to me made that [ ] and @ represent two different use cases: computed property access and private property access and for that reason there should be a syntactic distinction between them. Is this different from your first point above, about implementation optimization edge? The original name declaration via private x that Dave championed was definitely semantic: it created a new static lookup hierarchy, lexical but for names after . in expressions and before : in property assignments in object literals. This was, as Allen noted, controversial and enough respected folks on es-discuss (I recall Andrew Dupont in particular) and in TC39 reacted negatively that we separated and deferred it. I do not know how to revive it productively. Actually, I think you can blame be rather than Dave for the dual lookup hierarchy. Dave (and Sam's) original proposal did have special semantics for private name declarations bug only had a single lookup hierarchy. Of course -- sorry about that, credit and blame where due ;-). /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: using Private name objects for declarative property definition.
On Jul 8, 2011, at 2:58 PM, Brendan Eich wrote: On Jul 8, 2011, at 12:16 PM, Allen Wirfs-Brock wrote: The current Harmony classes proposal http://wiki.ecmascript.org/doku.php?id=harmony:classes includes the concept of private instance members and syntax for defining them. While it presents a syntax for accessing them (eg, private(foo).bar accesses the private 'bar' member of the object that is the value of foo) there does not yet appear to be consensus acceptance of this access syntax. Oh, quite the opposite -- everyone on TC39 with whom I've spoken agrees that private(this) syntax is straw that must be burned up. We need new syntax, or else we need to do at least what Dave proposed in minimal classes: defer private syntax, let programmers use private name objects and explicit [] indexing. I do think we can say a few more things about private in class that may not be in the requirements on the wiki: On the level of rationale for why class- and not instance-private, Juan Ignacio Dopazo in private correspondence made a good observation, shown by his example: class MyClass { private foo() {} bar() { var self = this; setTimeout(function () { self.foo(); }, 0); } } Because |this| is not lexical, instance- rather than class-private access that requires this. to the left of the private variable reference does not work unless you use the closure pattern explicitly and abjure |this|. The straw private(foo) syntax doesn't help if the goal is instance privacy, since the inner function has no idea (runtime, never mind compile-time) how to enforce to which particular instance self must refer. Requiring .bind(this) after the function expression passed to setTimeout can be used to work around such a hypothetical, mandatory this.-prefix instance-private restriction, but that's onerous and it can be too costly. An block lambdas would presumably also provide a solution if they were added to the language. Another observation about any class-private scheme we might consider in the current context: private instance variables in many ways (notably not for Object.freeze) act like properties, and the syntax mooted so far casts them in that light. Even if the ES5 reflective APIs, such as Object.getOwnPropertyNames, rightly skip privates on a class instance, proxies may raise the question: how does a private variable name reflect as a property name? class Point { constructor(x, y) { private x = x, y = y; } equals(other) { return private(this).x is private(other).x private(this).y is private(other).y; } ... } We cannot know how to ask for private-x from other without special syntax of some kind, either at the access point or as a binding declaration affecting all .x (and x: in object literals). So here I use the proposal's straw private(foo) syntax. Could other be a proxy that somehow has a private data record? Could other denote a class instance whose [[Prototype]] is a proxy? I claim we do not want private(foo) by itself, no .x after, to reify as an object, but if it did, then it seems to me it could be used with a proxy to reflect on private variable names. There are certainly several choices here, but the one I currently favor as simplest, which creates no new, ad-hoc concepts in the language, is that class-private instance variables are properties named by private name objects. There is more general issue about reflection on private members (and I intentionally said members here rather than names). Some of us believe that private members should never be exposed via reflection. Others of us think there are plenty of reflection use cases where it is either useful or necessary to reflect using private names. The current private name objects allows whether or not reflection is permitted using that name to be set when the private name is created. The pure declarative form in the proposed class declarations don't have any way to specify that regardless of whether or not private names are used as the underlyng implementation for private members). If it did allow such control (and private names were used in the implementation), there would still need to be a way to access the private name associated by the class declaration with a specific private member in order to reflect upon it. Sure you could use getOwnPropertyNames to access all of the property names but if that was all you had how could you be sure of the correspondence between private names and declared private members. Per the requirements for private in the classes proposal, this means Object.freeze does not freeze private-named properties, or at least does not make private-named data properties non-writable. Perhaps Object.preventExtensions should not restrict private-named properties from being added to the object. This seems strange and wrong at