typeof null
I think there is pretty broad consensus that this will affect the web. To what extent is unclear. I would be willing to take our nightly testers for a test drive for a couple days to see how badly things break if TC39 is leaning towards making such a change. Before we do that maybe you want to compile a browser with that change and surf around a bit and see how it goes? google, amazon, facebook, etc.
Hang on. The proposal is to change typeof null == "null" only under opt-in to Harmony.
I'll rewrite this as a harmony:typeof proposal for the next TC39 meeting, but it's already harmonious in concept.
Oh missed that wiki page. Good to see this going, created bugzilla.mozilla.org/show_bug.cgi?id=651251 anyways.
(Sorry brendan, didn't meant to write to you, post mailing list generation here)
Aware that that typeof null has been rejected, but I was wondering if it could be revived via the implicit "opt-in" path, eg:
non-strict, non-opt-in:
typeof null === "null"; // false
implied opt-in:
module Foo { export function create( options ) {
if ( typeof options === "null" ) {
return ... some default thing;
}
}; }
import create from Foo;
let default = create( null );
Is something like this even possible?
It looks to me like this code would work just fine by removing the word 'typeof'. What am I missing?
Wes Garland wrote:
It looks to me like this code would work just fine by removing the word 'typeof'. What am I missing?
Cross-frame/cross-context, too?
On Tue, May 8, 2012 at 12:42 PM, Wes Garland <wes at page.ca> wrote:
It looks to me like this code would work just fine by removing the word 'typeof'. What am I missing?
Sure, but that wasn't the point, I was simple trying to create an example piece of code that would trigger the "opt-in"
On Tue, May 8, 2012 at 12:52 PM, Herby Vojčík <herby at mailbox.sk> wrote:
Wes Garland wrote:
It looks to me like this code would work just fine by removing the word 'typeof'. What am I missing?
Cross-frame/cross-context, too?
I guess this answers my question, because if code can get from one frame to another, wherein the first has opted in, but the second has not, then there is breakage.
typeof is so quirky that I’m not sure fixing it is worth the trouble. It might make more sense to introduce a new mechanism that subsumes both typeof and instanceof and also took value objects into consideration (if there is a use case for doing so). Additionally, using typeof for finding out whether a variable is declared or not seems like a separate concern to me. If that was handled via a separate operator then one could prototype improved versions of typeof via libraries (because typeof unknownVariable not throwing is something you can’t implement via a library).
Axel
On May 8, 2012, at 9:19 AM, Rick Waldron wrote:
non-strict, non-opt-in:
typeof null === "null"; // false
implied opt-in:
Changing typeof null always seemed questionable to me in terms of value. It doesn't really give you significant new functionality, it just kinda seems "more sensible". But adding it would just make things more messy, for very little gain. Since we can't eliminate the old typeof semantics, we end up with the language having different semantics in different contexts.
And then once we moved to 1JS, it just seemed that much worse to have the variability, since the exact same code can have different meanings right near each other:
var x = typeof null;
module Foo {
export var y = typeof null;
}
console.log(x) // "object"
console.log(Foo.y) // "null"
It's just not worth it.
OTOH, we could pretty easily add a new library function:
import type from "@std";
console.log(type(null)) // "null"
I'd support that.
One possibility is to make type() work for all values:
switch(type(x)) { case Primitive.null: ... break; case Primitive.string: ... break; case String: ... break; }
That is, it would map primitive values to Primitive.* constants (or something similar, e.g. from a reflection API) and objects o to o.constructor.
Constants? We don't need no stinking constants! :-P Manifest strings, a la typeof, are the natural and self-describing upgrade path, if we're talking typeof (see Subject).
The Reflect namespace object is part of direct proxies, also populated in SpiderMonkey (not web-facing) by Reflect.parse to get an AST for a given source string. We could indeed put type-reflecting method(s) there but I'm loath to add manifest constants. What's wrong with strings?
Constants? We don't need no stinking constants! :-P Manifest strings, a la typeof, are the natural and self-describing upgrade path, if we're talking typeof (see Subject).
The Reflect namespace object is part of direct proxies, also populated in SpiderMonkey (not web-facing) by Reflect.parse to get an AST for a given source string. We could indeed put type-reflecting method(s) there but I'm loath to add manifest constants. What's wrong with strings?
Nothing – if it’s only about primitive values. But if you want to have something that is both Allen’s class
operator (for objects) and typeof
(for primitive values) then constants are the best solution (that I can think of). I’ve always thought that the distinction between typeof and instanceof was a bit artificial, it would be nice if we had something that unified both.
Or do you propose returning functions for objects and strings for primitives? E.g.:
switch(type(x)) { case "null": // x is null ... break; case "string": // typeof x === "string" ... break; case String: // x instanceof String ... break; }
Axel
Axel Rauschmayer wrote:
Constants? We don't need no stinking constants! :-P Manifest strings, a la typeof, are the natural and self-describing upgrade path, if we're talking typeof (see Subject).
The Reflect namespace object is part of direct proxies, also populated in SpiderMonkey (not web-facing) by Reflect.parse to get an AST for a given source string. We could indeed put type-reflecting method(s) there but I'm loath to add manifest constants. What's wrong with strings?
Nothing – if it’s only about primitive values. But if you want to have something that is both Allen’s
class
operator (for objects) andtypeof
(for primitive values) then constants are the best solution (that I can think of). I’ve always thought that the distinction between typeof and instanceof was a bit artificial, it would be nice if we had something that unified both.
The problem with constants is that users have to manage a closed (by ES version) manifest constant set. Polyfilling can't necessarily extend the Reflect constants (or can it? But then what's the advantage over strings?).
Unifying object [[Class]] with typeof -type can be done via strings too. The problem remains the closed set of constants.
Or do you propose returning functions for objects and strings for primitives? E.g.:
switch(type(x)) { case "null": // x is null ... break; case "string": // typeof x === "string" ... break; case String: // x instanceof String ... break; }
No. I would rather an upgrade for typeof, than something that tries to do two different things under one API.
Object class reflection is frowned upon in Smalltalk for a reason. We want protocols, structural conventions -- not nominal type tags. Or so I think!
On Tue, May 8, 2012 at 6:07 PM, Brendan Eich <brendan at mozilla.org> wrote:
Object class reflection is frowned upon in Smalltalk for a reason. We want protocols, structural conventions -- not nominal type tags. Or so I think!
Perhaps it would be helpful if someone made the case for typeof null === 'null'.
To me typeof null === 'object' is fine. It makes null a value in the space of 'object'. In practice I see 'null' used to mean "I know this reference (usually an argument) should be an object; I want to pass nothing but signal that I really did mean to pass nothing." The status quo allows this and it seems enough work for null to do for us.
Are there new things I can do if I now have a new answer to the 'typeof' question?
jjb
No. I would rather an upgrade for typeof, than something that tries to do two different things under one API.
Are those two things so different, though? Most people only need to know about the difference between primitive values and objects, because of typeof and instanceof.
If the “new typeof” is to be about classifying primitives and distinguishing primitive values from objects then I would make it return "object" for all non-primitive values (including functions).
On 5/8/2012 2:45 PM, David Herman wrote:
On May 8, 2012, at 9:19 AM, Rick Waldron wrote:
non-strict, non-opt-in:
typeof null === "null"; // false
implied opt-in:
Changing typeof null always seemed questionable to me in terms of value. It doesn't really give you significant new functionality, it just kinda seems "more sensible". But adding it would just make things more messy, for very little gain. Since we can't eliminate the old typeof semantics, we end up with the language having different semantics in different contexts.
The issue isn't typeof null. null === is a more convenient test. The issue is that typeof object gives a false positive when the value is null. So sensing that a value is an object is error prone. We need a simple, reliable test for objectness.
+1
this is something even seasoned js developers routinely trip over.
On 9 May 2012 01:32, Brendan Eich <brendan at mozilla.org> wrote:
We could indeed put type-reflecting method(s) there but I'm loath to add manifest constants. What's wrong with strings?
Depends on the mechanism. If it is supposed to be user-extensible (i.e. an open set) then strings are bad because there might be clashes between extensions made by separate libraries. With constants (or unique names) as denotations no such issue exists.
IOW, strings don't scale well. They are only suitable for closed sets.
Andreas Rossberg wrote:
On 9 May 2012 01:32, Brendan Eich<brendan at mozilla.org> wrote:
We could indeed put type-reflecting method(s) there but I'm loath to add manifest constants. What's wrong with strings?
Depends on the mechanism. If it is supposed to be user-extensible (i.e. an open set) then strings are bad because there might be clashes between extensions made by separate libraries. With constants (or unique names) as denotations no such issue exists.
IOW, strings don't scale well. They are only suitable for closed sets.
The problem is "who closes the set"? If the masses extend, you're right, but strings still can be made to work with awful hacks such as Java's reverse-DNS package naming. But I explicitly cited the set being closed by TC39, edition by edition. This can be done well with strings or enums of some kind (int-valued constants).
Then I argued strings win in the context of typeof.
I've prototyped int64 and uint64 value types, including Object.isValue based on the strawman:
js> Object.isValue(null)
true js> Object.isValue(undefined)
true js> Object.isValue(true)
true js> Object.isValue("hi")
true js> Object.isValue(18446744073709551615L) // uint64 literal
true js> Object.isValue({p: "foo"})
false js> Object.isValue(function(){})
false js> Object.isValue([1,2,3])
false
It's easy enough to revive your Object.isObject strawman that led to the typeof null === "null" attempt:
js> Object.isObject(null)
false js> Object.isObject(undefined)
false js> Object.isObject(true)
false js> Object.isObject("hi")
false js> Object.isObject(42)
false js> Object.isObject(18446744073709551615L) // uint64 literal
true js> Object.isObject({p: "foo"})
true js> Object.isObject(function(){})
true js> Object.isObject([1,2,3])
true
Will this help enough? In time, perhaps. Let's see how it stacks up:
if (typeof x == "object") x.oops(); // buggy
if (typeof x == "object" && x) x.oops(); // fixed until value objects
if (typeof x == "object" && x != null) x.sigh();
if (Object.isObject(x)) x.yeah();
To handicap properly I use == not === since the types of operands are guaranteed to make these operators equivalent in these particular uses. Object.isObject wins on brevity and it's pretty clear -- the repeated "Object" stem is a bit redundant but tolerable.
We should talk about reviving Object.isObject at the week after next's TC39 meeting. I'll put it on the agenda.
As Crock said, the problem programmers face is "I have a value, I want to treat it as an object and get a property (call a method, etc.). How do I test is-this-an-object so that I can use dot without worrying about that throwing right away on null left-hand side?"
On Wed, May 9, 2012 at 2:22 PM, Brendan Eich <brendan at mozilla.org> wrote:
As Crock said, the problem programmers face is "I have a value, I want to treat it as an object and get a property (call a method, etc.). How do I test is-this-an-object so that I can use dot without worrying about that throwing right away on null left-hand side?"
Yes, but ... why not if (foo) { foo.bar() } or one of its variants? I suppose that if (typeof foo === 'object') { ... helps in the case the your caller sent a string etc, but if this is your goal, then why not use if (foo && foo.bar) { and avoid other left-hand side problems?
(FWIW I don't really think this is a big deal one way or another).
jjb
John J Barton wrote:
On Wed, May 9, 2012 at 2:22 PM, Brendan Eich<brendan at mozilla.org> wrote:
As Crock said, the problem programmers face is "I have a value, I want to treat it as an object and get a property (call a method, etc.). How do I test is-this-an-object so that I can use dot without worrying about that throwing right away on null left-hand side?"
Yes, but ... why not if (foo) { foo.bar() } or one of its variants? I suppose that if (typeof foo === 'object') { ... helps in the case the your caller sent a string etc, but if this is your goal, then why not use if (foo&& foo.bar) { and avoid other left-hand side problems?
I like falsy tests but in really generic code, as you suggest, primitive falsy values could creep in and should work.
Testing for the wanted property being truthy may not be enough (if it must be callable) but at much production code does not have to guard against null because of guaranteed initialization or small-world codebase rules that fit in maintainers' heads.
(FWIW I don't really think this is a big deal one way or another).
Agreed. Adding Object.isObject this late won't help for downrev code and the polyfill, even though trivial, is probably not worth the trouble. My gut says so, anyway -- different developers may disagree.
I think we should consider Object.isObject just because the typeof null change replaced it, but 1JS killed that replacement. Also gives Object.isValue and Array.isArray some company ;-).
On May 9, 2012, at 11:43 PM, Brendan Eich wrote:
I think we should consider Object.isObject just because the typeof null change replaced it, but 1JS killed that replacement. Also gives Object.isValue and Array.isArray some company ;-).
Why not .isPrimitive()? We've always been talking about primitive values and objects, isn't it?
Are we going to have RegExp.isRegExp() and Date.isDate() and Number.isNumber() etc. too ?
Jorge wrote:
On May 9, 2012, at 11:43 PM, Brendan Eich wrote:
I think we should consider Object.isObject just because the typeof null change replaced it, but 1JS killed that replacement. Also gives Object.isValue and Array.isArray some company ;-).
Why not .isPrimitive()?
Could do that too, but Object.isPrimitive(x) would just be !Object.isObject(x).
We've always been talking about primitive values and objects, isn't it?
Indeed, sometimes also with "reference type" for objects (not to be confused with ECMA-262's "Reference" internal type).
Are we going to have RegExp.isRegExp() and Date.isDate() and Number.isNumber() etc. too ?
I did wince a bit about ES5's Array.isArray -- perhaps since it takes any x and tells whether x is an Array (from any window or frame) instance, it should have gone on object. You're right that the instanceof limitation motivating Array.isArray applies to these and other built-in "classes" as well.
Do we just add 'em all, or try to add only the ones for which we have enough experience to hope we're actually helping developers? How many times have you wanted RegExp.isRegExp, say? The belief is that testing for array-ness is more common. I don't have data, though, so I'm uncomfortable with any "need-based" approach.
Adding lots of predicates is straightforward, but then the dialectic requires us to wince at the sheer number of predicates (and the redundant name stems), and try for something like what Axel suggested: a do-it-all "type" or "typeOf" or "classify" API.
Comments welcome. I'm not sure what is best but lean toward predicates as goods in themselves, even if not the complete solution (if there is a complete solution).
Are we going to have RegExp.isRegExp() and Date.isDate() and Number.isNumber() etc. too ?
I did wince a bit about ES5's Array.isArray -- perhaps since it takes any x and tells whether x is an Array (from any window or frame) instance, it should have gone on object. You're right that the instanceof limitation motivating Array.isArray applies to these and other built-in "classes" as well.
Do we just add 'em all, or try to add only the ones for which we have enough experience to hope we're actually helping developers? How many times have you wanted RegExp.isRegExp, say? The belief is that testing for array-ness is more common. I don't have data, though, so I'm uncomfortable with any "need-based" approach.
Adding lots of predicates is straightforward, but then the dialectic requires us to wince at the sheer number of predicates (and the redundant name stems), and try for something like what Axel suggested: a do-it-all "type" or "typeOf" or "classify" API.
Comments welcome. I'm not sure what is best but lean toward predicates as goods in themselves, even if not the complete solution (if there is a complete solution).
It would be great if we could eliminate these predicates completely. How often does the frame crossing problem matter in practice? It doesn’t show up in non-browser environments, right?
I see several possibilities:
- Make instanceof work correctly with objects that have crossed frames.
- Introduce a binary predicate, e.g. likeInstanceOf that correctly handles cases where the lhs and rhs come from different contexts/frames.
Additionally, one could throw an exception if there is an instanceof check whose lhs and rhs are from different contexts (failing fast, preventing obscure and hard-to-debug errors).
Axel Rauschmayer wrote:
It would be great if we could eliminate these predicates completely. How often does the frame crossing problem matter in practice? It doesn’t show up in non-browser environments, right?
Those matter but not so much. I know, Node.js -- I'm a fan. But really, we have a large user base that does, I'm told, often enough face the problem that !(someArrayFromAnotherWindow instanceof Array).
I see several possibilities:
- Make instanceof work correctly with objects that have crossed frames.
We can't do this. It will break code that knows how instanceof works
On May 10, 2012, at 1:08 AM, Axel Rauschmayer wrote:
Are we going to have RegExp.isRegExp() and Date.isDate() and Number.isNumber() etc. too ?
I did wince a bit about ES5's Array.isArray -- perhaps since it takes any x and tells whether x is an Array (from any window or frame) instance, it should have gone on object. You're right that the instanceof limitation motivating Array.isArray applies to these and other built-in "classes" as well.
Do we just add 'em all, or try to add only the ones for which we have enough experience to hope we're actually helping developers? How many times have you wanted RegExp.isRegExp, say? The belief is that testing for array-ness is more common. I don't have data, though, so I'm uncomfortable with any "need-based" approach.
Adding lots of predicates is straightforward, but then the dialectic requires us to wince at the sheer number of predicates (and the redundant name stems), and try for something like what Axel suggested: a do-it-all "type" or "typeOf" or "classify" API.
Comments welcome. I'm not sure what is best but lean toward predicates as goods in themselves, even if not the complete solution (if there is a complete solution).
It would be great if we could eliminate these predicates completely. How often does the frame crossing problem matter in practice? It doesn’t show up in non-browser environments, right?
I see several possibilities:
- Make instanceof work correctly with objects that have crossed frames.
- Introduce a binary predicate, e.g. likeInstanceOf that correctly handles cases where the lhs and rhs come from different contexts/frames.
Additionally, one could throw an exception if there is an instanceof check whose lhs and rhs are from different contexts (failing fast, preventing obscure and hard-to-debug errors).
When you want to dispatch based on a type, a fixed typeof (one that worked well) would be better:
switch (typeof x)
case "Array":
case "RegExp":
case ...
else you'd have to do:
if (Array.isArray(x)) ... else if (RegExp.isRegExp(x)) ... else if ( etc )
When you just want to assert that x is of type Type, then an if (Type.isType(x)) would be ok, but a proper typeof would do just as well: if (typeof x === "Type")
So it seems that a new, fixed typeof would be best?
And if the new, fixed typeof were typeOf(), with capital O, a global function instead of a language keyword/operator? That would be easily polyfill-able.
- Introduce a binary predicate, e.g. likeInstanceOf that correctly handles cases where the lhs and rhs come from different contexts/frames.
Again, how? The name of the constructor in one frame may not be relevant in the other. The constructor may not be a built-in. Are you thinking in nominal type terms again? :-/
Strawman for a polyfill:
function hasClass(value, type) {
// Possibly: throw if `type` is not a built-in or does not have a property `name`
return Object.prototype.toString.call(value) === "[object "+type.name+"]";
}
Interaction:
$ hasClass([], Array)
true
$ hasClass([], RegExp)
false
Alternative: work with string values.
function hasClass(value, typeName) {
if (typeof typeName !== "string") {
throw new TypeError("...");
}
return Object.prototype.toString.call(value) === "[object "+typeName+"]";
}
Interaction:
$ hasClass([], "Array")
true
$ hasClass([], "RegExp")
false
Admittedly, this solution has its own problems. But, IMO, it is an improvement to having one predicate per builtin. Plus, it would work with non-builtins, too. A function getClassName() (as suggested by Jorge) would work just as well.
$ getClassName(arrayFromAnotherWindow)
"Array"
$ getClassName(regexFromAnotherWindow)
"RegExp"
Do modules change anything? Are module instances shared between windows? Will there be a unique way of identifying types? Are there any other ideas for making the cross-window situation simpler?
On May 9, 2012, at 4:08 PM, Axel Rauschmayer wrote:
Are we going to have RegExp.isRegExp() and Date.isDate() and Number.isNumber() etc. too ?
I did wince a bit about ES5's Array.isArray -- perhaps since it takes any x and tells whether x is an Array (from any window or frame) instance, it should have gone on object. You're right that the instanceof limitation motivating Array.isArray applies to these and other built-in "classes" as well.
Do we just add 'em all, or try to add only the ones for which we have enough experience to hope we're actually helping developers? How many times have you wanted RegExp.isRegExp, say? The belief is that testing for array-ness is more common. I don't have data, though, so I'm uncomfortable with any "need-based" approach.
Adding lots of predicates is straightforward, but then the dialectic requires us to wince at the sheer number of predicates (and the redundant name stems), and try for something like what Axel suggested: a do-it-all "type" or "typeOf" or "classify" API.
Comments welcome. I'm not sure what is best but lean toward predicates as goods in themselves, even if not the complete solution (if there is a complete solution).
It would be great if we could eliminate these predicates completely. How often does the frame crossing problem matter in practice? It doesn’t show up in non-browser environments, right?
I see several possibilities:
- Make instanceof work correctly with objects that have crossed frames.
- Introduce a binary predicate, e.g. likeInstanceOf that correctly handles cases where the lhs and rhs come from different contexts/frames.
The reason there will never be a single solution to these issue is that desirable behavior varies depending upon what the programmer is trying to accomplish. Just using (possibly cross-frame) arrays an an example. What is the programmer really asking when they say "is this object an Array". Do the need to know whether it has array indexed properties and a length property? Do they need to know where it maintains the special Array length invariant? Do they need to know whether it implements it has properties that implement the array extra methods? Do they need to know that in inherits from the build-in array prototype object? Or, do they just need to know whether they should serialize the object using [ ] instead of { } notation?
The last one was the situation that jason2.js ran into WRT cross-frame arrays and it provided the use case that lead to Array.isArray. But it isn't clear that was the best possible solution. Another solution might have been to add a serializeAsArray property to Array.prototype. That would work cross frame and it would have made it easier to for programmer to define there own internal abstractions that would also serialize using [ ] notation.
Whenever anybody says they need to do a type test (or instanceof test) we need to ask them: Why? What is it you really need to know. Smalltalkers learned fairly early that type/class testing was an anti-pattern. It makes code brittle and difficult to fix or extend.
I'm not sure what a cross-frame instanceof test (that didn't always return false) would mean. I understand why json2.js thought it needed to do a cross-frame isArray test. I would like to understand other real use cases where cross-frame type testing is needed. Does anybody have any?
Good points. In hindsight, my complaint was more about things currently being a bit confusing (one frequently reads that one shouldn’t use instanceof Array) than about proposing a specific solution. So the comment below helps.
On this tangent, I personally almost always mean "isArrayish" in this very common situation. That is, indexed with a length such that almost all Array.prototype methods would work on it out of the box. The number of arrayish interfaces provided by host environments has continued to grow. It's the epitome of generic, easily extended, usable, and efficient interfaces. Sometimes duck typing goes from "walks like a duck" to "is a vertebrate and is a bird and can fly and is found in sub-tropical areas and walks like a duck and quacks like a duck and can mate with ducks (offspring optional)".
The following is the shortest check I can think of to accomplish this goal accurately. It isn't guaranteed to work with sparse collections but that are rare aside from built-in arrays themselves.
function isIndexed(o){ return Boolean(o) && hasOwn(o, 'length') && hasOwn(o, o.length - 1); }
The reason there will never be a single solution to these issue is that
On May 10, 2012, at 1:05 AM, Brandon Benvie wrote:
On this tangent, I personally almost always mean "isArrayish" in this very common situation. That is, indexed with a length such that almost all Array.prototype methods would work on it out of the box. The number of arrayish interfaces provided by host environments has continued to grow. It's the epitome of generic, easily extended, usable, and efficient interfaces. Sometimes duck typing goes from "walks like a duck" to "is a vertebrate and is a bird and can fly and is found in sub-tropical areas and walks like a duck and quacks like a duck and can mate with ducks (offspring optional)".
IMO, abstract structural interfaces (duck types) and type testing don't mix very well. That is, duck typing works smoothest if you don't explicitly ask if something's a duck, you just document "I expect a duck here. It's your responsibility to provide me a sufficiently duck-like value."
If you want an interface that overloads a duck type with other types, e.g., one that expects either an arrayish or a string or a number, you have a couple options. You can make the arrayish the catchall case:
function f(x) {
switch (typeof x) {
case "string":
// do the string thing
break;
case "number":
// do the number thing
break;
default:
// do the arrayish thing
break;
}
}
Or you can require the programmer to provide an explicit tag in another way. For example, you can have them tag the value with an object property:
f({ name: "gray" })
f({ hex: 0x080808 })
f({ rgb: [127, 127, 127] })
The following is the shortest check I can think of to accomplish this goal accurately. It isn't guaranteed to work with sparse collections but that are rare aside from built-in arrays themselves.
I'm not a fan of this approach. When you start say "those are rare" what you're saying is that there's a fuzzy boundary between the different kinds of things you accept. Either you don't really know what that boundary is, which is an invitation to bugs, or the boundary is complicated enough that your clients will be likely to be confused about it even if you document it very carefully.
function isIndexed(o){ return Boolean(o) && hasOwn(o, 'length') && hasOwn(o, o.length - 1); }
If you overload, say, the types object and arrayish in your interface, then any time an object happens to have a length property and an n - 1 property it'll get unexpectedly categorized in the arrayish bucket. For example:
var stringConstants = makeDict();
stringConstants.length = "length;
stringConstants.NaN = "NaN";
...
isIndexed(stringConstants) // true
IMO it's better to keep the tests used for distinguishing between different types of input simple and obvious. For duck types, there's no straightforward test you can do, so it's better to wrap a duck type in a container that's easily testable if you want to overload it with other types and explicitly test for that case.
Axel Rauschmayer wrote:
Are we going to have RegExp.isRegExp() and Date.isDate() and Number.isNumber() etc. too ?
I did wince a bit about ES5's Array.isArray -- perhaps since it takes any x and tells whether x is an Array (from any window or frame) instance, it should have gone on object. You're right that the instanceof limitation motivating Array.isArray applies to these and other built-in "classes" as well.
Do we just add 'em all, or try to add only the ones for which we have enough experience to hope we're actually helping developers? How many times have you wanted RegExp.isRegExp, say? The belief is that testing for array-ness is more common. I don't have data, though, so I'm uncomfortable with any "need-based" approach.
Adding lots of predicates is straightforward, but then the dialectic requires us to wince at the sheer number of predicates (and the redundant name stems), and try for something like what Axel suggested: a do-it-all "type" or "typeOf" or "classify" API.
Comments welcome. I'm not sure what is best but lean toward predicates as goods in themselves, even if not the complete solution (if there is a complete solution).
It would be great if we could eliminate these predicates completely. How often does the frame crossing problem matter in practice? It doesn’t show up in non-browser environments, right?
But of course they do. Where do you draw your conclusion they don't? There is vm module in node.js which runs code in different contexts; webos-builtin nodejs uses context-per-module if I am not mistaken.
Allen Wirfs-Brock wrote:
On May 9, 2012, at 4:08 PM, Axel Rauschmayer wrote:
Are we going to have RegExp.isRegExp() and Date.isDate() and Number.isNumber() etc. too ? I did wince a bit about ES5's Array.isArray -- perhaps since it takes any x and tells whether x is an Array (from any window or frame) instance, it should have gone on object. You're right that the instanceof limitation motivating Array.isArray applies to these and other built-in "classes" as well.
Do we just add 'em all, or try to add only the ones for which we have enough experience to hope we're actually helping developers? How many times have you wanted RegExp.isRegExp, say? The belief is that testing for array-ness is more common. I don't have data, though, so I'm uncomfortable with any "need-based" approach.
Adding lots of predicates is straightforward, but then the dialectic requires us to wince at the sheer number of predicates (and the redundant name stems), and try for something like what Axel suggested: a do-it-all "type" or "typeOf" or "classify" API.
Comments welcome. I'm not sure what is best but lean toward predicates as goods in themselves, even if not the complete solution (if there is a complete solution).
It would be great if we could eliminate these predicates completely. How often does the frame crossing problem matter in practice? It doesn’t show up in non-browser environments, right?
I see several possibilities: - Make instanceof work correctly with objects that have crossed frames. - Introduce a binary predicate, e.g. likeInstanceOf that correctly handles cases where the lhs and rhs come from different contexts/frames.
The reason there will never be a single solution to these issue is that desirable behavior varies depending upon what the programmer is trying to accomplish. Just using (possibly cross-frame) arrays an an example. What is the programmer really asking when they say "is this object an Array". Do the need to know whether it has array indexed properties and a length property? Do they need to know where it maintains the special Array length invariant? Do they need to know whether it implements it has properties that implement the array extra methods? Do they need to know that in inherits from the build-in array prototype object? Or, do they just need to know whether they should serialize the object using [ ] instead of { } notation?
The last one was the situation that jason2.js ran into WRT cross-frame arrays and it provided the use case that lead to Array.isArray. But it isn't clear that was the best possible solution. Another solution might have been to add a serializeAsArray property to Array.prototype. That would work cross frame and it would have made it easier to for programmer to define there own internal abstractions that would also serialize using [ ] notation.
Whenever anybody says they need to do a type test (or instanceof test) we need to ask them: Why? What is it you really need to know. Smalltalkers learned fairly early that type/class testing was an anti-pattern. It makes code brittle and difficult to fix or extend.
Yes, smalltalkers frown upon isKindOf: which is like JS instanceof. But they have their isNumber, isInteger, isNil, isWhateverElse methods. At least for "built-ins" they can be pretty sure than sending isInteger returns true for any object that pretends to be integer, even in case it would be a proxy from different Smalltalk (I am trying to make a case analogous to cross-frame). So for these "built-in" types, checking that works cross-frame should probably be there (akin to Array.isArray).
Brendan Eich wrote:
John J Barton wrote:
On Wed, May 9, 2012 at 2:22 PM, Brendan Eich<brendan at mozilla.org> wrote:
As Crock said, the problem programmers face is "I have a value, I want to treat it as an object and get a property (call a method, etc.). How do I test is-this-an-object so that I can use dot without worrying about that throwing right away on null left-hand side?"
Yes, but ... why not if (foo) { foo.bar() } or one of its variants? I suppose that if (typeof foo === 'object') { ... helps in the case the your caller sent a string etc, but if this is your goal, then why not use if (foo&& foo.bar) { and avoid other left-hand side problems?
I like falsy tests but in really generic code, as you suggest, primitive falsy values could creep in and should work.
Testing for the wanted property being truthy may not be enough (if it must be callable) but at much production code does not have to guard against null because of guaranteed initialization or small-world codebase rules that fit in maintainers' heads.
Here, it seems to me, the best solution would be built-in operators that test this:
canget foo.bar cancall foo.bar canconstruct foo.bar
which operate on spec-References.
On May 10, 2012, at 1:05 AM, Brandon Benvie wrote:
On this tangent, I personally almost always mean "isArrayish" in this very common situation. That is, indexed with a length such that almost all Array.prototype methods would work on it out of the box. The number of arrayish interfaces provided by host environments has continued to grow. It's the epitome of generic, easily extended, usable, and efficient interfaces. Sometimes duck typing goes from "walks like a duck" to "is a vertebrate and is a bird and can fly and is found in sub-tropical areas and walks like a duck and quacks like a duck and can mate with ducks (offspring optional)".
The following is the shortest check I can think of to accomplish this goal accurately. It isn't guaranteed to work with sparse collections but that are rare aside from built-in arrays themselves.
function isIndexed(o){ return Boolean(o) && hasOwn(o, 'length') && hasOwn(o, o.length - 1); }
Note that the "generic" Array.prototype methods get away without have to do a "isArrayish" classification test. In general, the Array.prototype methods are specified such that all objects appear "arrayish" to them. They do this by accessing the "length" property and doing a ToUint32 conversions
On May 10, 2012, at 2:10 AM, Herby Vojčík wrote:
Allen Wirfs-Brock wrote:
...
Whenever anybody says they need to do a type test (or instanceof test) we need to ask them: Why? What is it you really need to know. Smalltalkers learned fairly early that type/class testing was an anti-pattern. It makes code brittle and difficult to fix or extend.
Yes, smalltalkers frown upon isKindOf: which is like JS instanceof. But they have their isNumber, isInteger, isNil, isWhateverElse methods. At least for "built-ins" they can be pretty sure than sending isInteger returns true for any object that pretends to be integer, even in case it would be a proxy from different Smalltalk (I am trying to make a case analogous to cross-frame). So for these "built-in" types, checking that works cross-frame should probably be there (akin to Array.isArray).
Or they use an explicit class check: anObj class == Array which is also frowned upon.
So, yes, situations where behavioral classification needs to be done in Smalltalk is preferably done with isFoo methods. They are better than class (including isKindOf:) tests because they decouple behavioral classification from the implementation class hierarchy. However, the downside of such methods (in Smalltalk) is that they generally require monkey patching the negative case into class Object. EG, add to class Object's instance behavior the method: isFoo "Only classes that explicitly over-ride this to return true are considered to be fooish" ^false
This has similar maintenance issues to what people see in JS when they start augmenting the built-in prototypes with new properties.
For this use case that problem is avoidable in JS. Since accessing a missing property property returns a falsey value the negative case doesn't have to be monkey patched into into Object.prototype.
var obj1 = { isFoo: true; doFooishThings: function() {...} }; var obj2 = { }; if (obj1.isFoo) obj1.doFooishThings(); //call the method if (obj2.isFoo) obj1.doFooishThings(); //does not try to call the method
Note that in this and many other similar situations the isFoo method is actually redundant. You would get the same effect by saying:
if (obj1.doFooishThings) obj1.doFooishThings();
this suggests that a useful operator might be a conditional property access, perhaps something like:
??obj.doFooishThings();
which parses as CallExpression : ?? MemberExpression Arguments
If conditionally calls the value of the MemberExpression if it is not undefined.
Allen Wirfs-Brock wrote:
Note that in this and many other similar situations the isFoo method is actually redundant. You would get the same effect by saying:
if (obj1.doFooishThings) obj1.doFooishThings();
this suggests that a useful operator might be a conditional property access, perhaps something like:
??obj.doFooishThings();
which parses as CallExpression : ?? MemberExpression Arguments
If conditionally calls the value of the MemberExpression if it is not undefined.
CoffeeScript has foo?.bar ... and foo.baz?(...) in lieu of
if (foo != null) foo.bar ...
and
if (foo.baz != null) foo.baz(...)
where all ... are meta. We've talked about these a bit in TC39 but I don't recall any definite outcome.
Brendan Eich wrote:
CoffeeScript has foo?.bar ... and foo.baz?(...) in lieu of
if (foo != null) foo.bar ...
and
if (foo.baz != null) foo.baz(...)
Correction: the ?( operator compiles like so:
$ echo 'foo={};foo.baz?()' > /tmp/foo2.cs $ ./bin/coffee -p !$ ./bin/coffee -p /tmp/foo2.cs (function() { var foo;
foo = {};
if (typeof foo.baz === "function") { foo.baz(); }
}).call(this);
Quick, possibly silly idea: What if typeof null
returned undefined
?
Alex Rauschmayer wrote:
Quick, possibly silly idea: What if
typeof null
returnedundefined
?
By default or after you flip a switch?
Nathan Wall wrote:
Alex Rauschmayer wrote:
Quick, possibly silly idea: What if
typeof null
returnedundefined
?By default or after you flip a switch?
I have a better value objects draft coming, which addresses opt-in typeof reform (cross-realm!).
In the mean time, typeof null == "object" and anything else will break the web. Doesn't matter whether "null" or "undefined" -- too much code requires typeof null == "object". Someone from the V8 team should weigh in with the bug link(s).
Hey list,
just wanted to ask what's going to happen with typeof null, i am a lot in the favor of changing it to say "null". Changing this was discussed here strawman:object_isobject and proposals:typeof. I think ´typeof null´ could use it's own proposal and some tracking how much this would affect the web.