Another Javascript surprise: undefined vs undefined



  • You'd say that an array with undefined values is an array with undefined values, right? Not in Javascript. There is a difference between var a = new Array(3), and var b = [undefined, undefined, undefined]. Both are arrays with 3 undefined values, and a[i] === b[i]. They behave identical in a loop. So far so good. But now try a.map(fun) vs b.map(fun). The second one returns [fun(undefined), fun(undefined), fun(undefined)], as you expect, but the first one returns something like new Array(3): 3 undefined values, and fun never gets called. And filter is even less consistent: a.filter(fun) simply returns [], whatever function you provide.

    Yes, it gets mentioned in the documentation (of map, but not of filter), but ... WHY? Who the fuck thought that would be a good idea?



  • It is silly, but I'm surprised this came up in general use. Undefined is meant for comparison purposes only and isn't anything you should be assigning to variables or properties; null is a better choice for a reference to nothing. If you ever need to serialize this object or another containing it to JSON, the spec doesn't cover undefined: http://www.json.org/

    Though you probably knew this and I just came across as incredibly condescending and arrogant.

    Out of interest, what's the use case for using the array constructor rather than an anonymous array? I used to use it back when I thought everything was C#/Java.



  • There was already a thread about this at some point.

    var a = Array(3) creates a sparse array, that has the declared length of 3, but no values. When you do a[1], javascript checks value at 1, finds nothing, and returns the undefined "token".

    var b = [undefined, undefined, undefined], on the other hand, is an array that is "filled" with 3 undefined tokens. Even though a[1] === b[1] is true, these are two different arrays.

    Note that there are implications with memory usage and performance. A sparse array with length of a billion will take no memory at all. An array with a billion undefineds will take the memory for a billion pointers.

    As for whether this is a good idea.... Well, when you think about it, this is inevitable when you have a first class value used as placeholder for "key not found", instead of throwing an error. They could have prevented you from feeding undefined values into hashes and arrays, but I guess no one thought of that during the two week period the language was designed in, and now it's tool late.



  • But how can you have a sparse array with a length of three but nothing in it? (rather than an array with items at indexes and spaces between) That doesn't really make any sense. Guess if you have some code which will for(var i = 0... over it it's useful shorthand sometimes.



  • @nexekho said:

    That doesn't really make any sense.

    I thought that was the point of designing a programming language in a single week?



  • @nexekho said:

    But how can you have a sparse array with a length of three but nothing in it? (rather than an array with items at indexes and spaces between) That doesn't really make any sense. Guess if you have some code which will for(var i = 0... over it it's useful shorthand sometimes.

    I guess it is declared with the length 3, but no memory is actually allocated underneath it.

    The real question is, will this array: var a = Array(3); a[2] = undefined;, take memory for 3 pointers or 1?



  • Javascript is so high-level that you shouldn't (ideally, mustn't) care about it. The only exception is optimization - but when optimizing, the only answer you should accept for your question is profiler's output.



  • @cartman82 said:

    Note that there are implications with memory usage and performance.

    That still allows map and filter to iterate over all elements, without any extra cost. There is really no reason to make the implementation show through only when using a specific function.

    @cartman82 said:

    this is inevitable when you have a first class value used as placeholder for "key not found", instead of throwing an error

    Not really. new Array(3) creates an array of three elements, so it has indices/keys 0, 1 and 2.

    Nope, it just doesn't make any sense.


  • sockdevs

    @Hanzo said:

    var a = new Array(3),

    yeah, that won't do what you want....

    // new-array polyfill that does the "right" thing
    Array.prototype.newArray = function(length) {
        return Array.apply(null, Array(length)).map(function () {
            return null;
        });
    }
    

    Try using that instead. ;-)



  • @nexekho said:

    It is silly, but I'm surprised this came up in general use. Undefined is meant for comparison purposes only and isn't anything you should be assigning to variables or properties; null is a better choice for a reference to nothing.
    I agree, but there are a lot of people who will tell you:

    Null = Undefined
    Null != Nothing


  • mod

    @Hanzo said:

    new Array(3) creates an array of three elements

    No it doesn't. It creates an object, of type Array, with a length property (value: 3) and no other properties defined. Therefore, a.banana, a['banana'], and a[0] all return undefined, as they are not defined members. The only thing that's defined is a.length, which is 3.

    Don't get this confused with C-style arrays, it's totally different.



  • [,,,] is equivalent to new Array(3). No, that is not a fencepost error.


  • Winner of the 2016 Presidential Election

    PHP does something similar where the last trailing comma doesn't generate an extra item.



  • Go requires a comma after any complete value inside a compound literal that has a newline before the closing brace.


  • Discourse touched me in a no-no place

    @ben_lubar said:

    Go requires a comma after any complete value inside a compound literal that has a newline before the closing brace.

    [insert [this bothers my ocd] macro here]



  • That's correct style in C#, too.


  • Discourse touched me in a no-no place

    @blakeyrat said:

    That's correct style in C#, too.

    I'm not sure if I knew that or not. It just seems wrong, though.



  • Ok first of all, "seems wrong" is an emotional outburst that doesn't belong in software development.

    It makes it much easier to copy lists of stuff around. It constantly bugs the crap out of me that SQL doesn't allow it, so if I paste my list of enums (or whatever) into a SQL query, then I have to cursor up and delete the last comma every goddamned time.


  • Discourse touched me in a no-no place

    @blakeyrat said:

    Ok first of all, "seems wrong" is an emotional outburst that doesn't belong in software development.

    It doesn't comport with human language syntax, OK? Time to recalibrate your shoulder aliens again.

    @blakeyrat said:

    It makes it much easier to copy lists of stuff around.

    That actually makes sense. But from the perspective of someone who's not used to it it seems weird/unusual.



  • @Yamikuronue said:

    @Hanzo said:
    new Array(3) creates an array of three elements

    No it doesn't. It creates an object, of type Array, with a length property (value: 3) and no other properties defined.

    You're living in denial. If it walks like a duck and quacks like a duck, it should iterate like a duck.


  • Winner of the 2016 Presidential Election

    @Hanzo said:

    If it walks like a duck and quacks like a duck, it should iterate like a duck.

    Is the cycle of duck reproduction something we really needed in a programming language?



  • JavaScript is about as fucked up as a duck's reproductive process.


  • Impossible Mission Players - A

    @ben_lubar said:

    about as fucked up

    Close, maybe...

    SCIENTIFICALLY ACCURATE ™: DUCKTALES – 02:02
    — Animation Domination High-Def


    [Trigger warning: Corkscrew]



  • It's always fun to see someone experience JS sparse arrays for the first time.

    They're actually more like hash maps. Array functionality is mainly just provided for convenience.

    @Hanzo said:

    new Array(3) creates an array of three elements, so it has indices/keys 0, 1 and 2.

    No, it doesn't:

    Basically, any object with a length property can be used as a duck-typed array (including strings). If it's not an array, it won't have the Array.prototype methods, although even then it's easy enough to bind one of them to it and it'll happily process it as if it was an array:

    The question of "is x an array or not" is actually complicated enough that there's a dedicated function for it now: Array.isArray(x).



  • Also worth mentioning is that "array" keys/indices are actually strings, and anything that can be cast to a string (i.e., anything) can be used as an array index.

    You can quite easily run into situations like this:



  • @anotherusername said:

    Also worth mentioning is that "array" keys/indices are actually strings, and anything that can be cast to a string (i.e., anything) can be used as an array index.

    That is the worst part. And the empty string is a legal property name too. For comical value, do a=[]; a[a] = a; then ask what the value of a[a+a] is :laughing:



  • The really fun part is if you've changed the object's toString function.


  • Impossible Mission Players - A

    No repro: :frowning:



  • That is the repro. You recursively assigned a to a[""], then you accessed a["" + ""] and it returned a. Note that it doesn't print the "" property of a when it displays it, since a is an array. You could confirm that it has one, though, if you execute a.hasOwnProperty("").


  • Impossible Mission Players - A

    @anotherusername said:

    That is the repro
    Ah. Apparently my understanding of the language has been found... wanting.



  • Also, since a is now recursive, you can follow the rabbit hole forever...


  • Impossible Mission Players - A

    @anotherusername said:

    follow the rabbit hole forever...
    :giggity:

    Nice!



  • a[a][a][a][a][a][a][a] would work, as well.



  • @Tsaukpaetra said:

    Ah. Apparently my understanding of the language has been found... wanting.

    The joke was that in Javascript you can sow so much confusion with just one variable. I propose you refuse to understand these parts.

    @anotherusername said:

    The really funevil part is if you've changed the object's toString function.

    I will have no part in this :smiley:

    @anotherusername said:

    a[a][a][a][a][a][a][a] would work, as well.

    :scream: aaaaaaaaaaaaaaaaaaaaaaaaa




  • Discourse touched me in a no-no place

    @anotherusername said:

    Also, since a is now recursive, you can follow the rabbit hole forever...

    And people at work wonder why I like constructively finite data structures…



  • Who doesn't like undefined for a property name? I sure do. And I dare you to muster a prediction what this evaluates to :smiley:

    2*undefined !== undefined+undefined
    

    It is nasty on two levels, I got it wrong for the wrong reasons. Here's a hint to throw you off-track:
    [spoiler] ```
    a={};
    a[2*undefined] = a;
    a[undefined+undefined] === a



  • Let's try that:

    2undefined = NaN, because the multiplication triggers type coercion on undefined, which becomes NaN and 2NaN yields NaN.

    undefined + undefined = "undefinedundefined" because the + operator means concatenation unless both operands are numbers.

    So I'd have to say that your expression reduces to NaN !== "undefinedundefined" which is true.



  • Let me guess...

    2 * undefined and undefined + undefined are both NaN

    NaN !== NaN (Actually, NaN != NaN, too)

    But... the array index is a string, and "NaN" === "NaN".

    I actually thought about using that (NaN) for my "x and y are not equal, but a[x] and a[y] reference the same array element" example, but I decided that two different objects would be a better example.



  • @LordOfThePigs said:

    undefined + undefined = "undefinedundefined" because the + operator means concatenation unless both operands are numbers.

    Also...



  • @LordOfThePigs said:

    So I'd have to say that your expression reduces to NaN !== "undefinedundefined" which is true.

    You fared way better than I did. You got the result right :smile:

    @anotherusername said:

    NaN !== NaN (Actually, NaN != NaN, too)

    Right! Somehow I expected NaN !== NaN to be false because NaN === NaN is false (like the SQL NULL semantics.)


  • area_deu

    You scared me.

    I thought my Firefox actually crashed for a moment.



  • @Arantor said:

    PHP does something similar where the last trailing comma doesn't generate an extra item

    I was almost reconsidering my hate for php because of its low hardware requirements (discourse made me think about that in another server)

    You just made me hate it again.

    :edit: I misread your post, thought php did what js did in op


  • Winner of the 2016 Presidential Election

    But... but...

    Improved performance: PHP 7 is up to twice as fast as PHP 5.6

    Imagine the speed benefits over Discourse! :trollface:



  • I grabbed my pitchfork too early, I read that as php doing what javascript did in op.

    You triggered me man



  • @nexekho said:

    But how can you have a sparse array with a length of three but nothing in it?

    That is literally the definition of a sparse array. That's what the "sparse" part means: not all slots contain a value. And "0 slots contain a value" certainly qualifies as "not all slots contain a value" for any array size > 0.



  • @Gaska said:

    Javascript is so high-level that you shouldn't (ideally, mustn't) care about it.

    How about "are not allowed to, by design":

    In [plain] English: the philosophy of JavaScript (to the extent that it has any philosophy) is that **you should not be able to observe what is going on in system memory, full stop**. This is so unbelievably out of touch with how real people write mobile applications, I can’t even find the words to express it to you. .. You need *serious, formal memory management guarantees* on mobile. And JavaScript, **by design,** refuses to provide them.


  • @gleemonk said:

    That is the worst part. And the empty string is a legal property name too. For comical value, do a=[]; a[a] = a; then ask what the value of a[a+a] is :laughing:

    Wat...



  • @Mason_Wheeler said:

    How about "are not allowed to, by design"

    This article makes it look like it was a bad design decision.



  • @Gaska said:

    This article makes it look like it was a bad design decision.

    ...are you saying it wasn't?


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.