Transcript from the "Abstract Value Operations" Lesson
>> Kyle Simpson: I wanna start first on this topic of coercion by digging a little bit into the nitty-gritty of some stuff that the spec talks about here, okay? And I know we're gonna be down in the weeds for a few minutes. You gotta just stick with me because I'm gonna bring us back up to a higher level of understanding but I don't want us to use terminology that you aren't familiar with.
[00:00:20] And if you haven't read the spec, there is some terminology that definitely might be a little confusing. So, the spec refers to a certain set of operations called abstract operations that represent these things that we're going to do and an example that we're going to start with is the ToString abstract operation.
[00:01:03] All over the spec whenever they wanna do that, they just dispatch, it's like an internal built in function of the language. It is just called ToString. Capital T ToString. Now, these abstract operations aren't actually implemented like there is a real function that you could call that way there sort of guidelines there typically in line whenever the engine is implementing things.
[00:01:29] But they're useful for us to think about in terms of, if we can observe something in our code and we can say, I know under the covers that's doing this particular abstract operation, then we have an algorithm to inspect that tells us exactly what that abstract operation's gonna do.
[00:01:47] So any time we have some value that's not already a string, and we wanna make it into a string, these are the sets of things that are going to happen. Rather than describe all of the internals of that operation, I just want to list some examples of what it was.
[00:02:03] You will notice that pretty much all of these are pretty intuitive. If we take the no value and we call that abstract two string operation on it we are going to get quote no. Undefined is quote undefined, true, false, the number. So the only that's weird is that negative 0.
[00:02:17] And I already told you about that one before. The two strings pre-configured to lie. When it's given the negative 0, it's pre-configured to lie and say quote 0. All the rest of them make complete sense when call on these primitive value types.
>> Kyle Simpson: When two string is called upon a non-primitive, that is like one of the objects, like for example, in array, it's a little bit more complicated.
[00:02:43] Okay. So, when we call it against the array, it's going to dispatch against the actual two-string method of that value. Either the default one or one that you have customized. So the default to string method when called upon an array is going to do the following things, and I find these things a little bit bizarre, the fact that an empty array when we stringify it becomes the empty string, I find troubling.
[00:03:10] Because if I have an empty string I don't know if that came from an empty string or if it came from an empty array. To me, the stringification of an empty array should have the square brackets in it with no contents in it. Similar to what it would look like of you JSON stringified it, right?
[00:03:24] The JSON string representation, that to me is the most natural string representation for an array. Here, they leave off the brackets. You'll notice that on array 1, 2, 3, they just put the 1, 2, 3, and not the brackets around it. So I find this to be a bizarre and puzzling one, but it also happens to create some other problems for us down the road.
[00:03:43] So this is one of those mistakes that I wish we could go back and fix. Cuz we could reduce a whole lot of the developer confusion if we would fix some of these early design decisions make the more sence. But the biggest one that really bothers me on this whole slide is the fact that when no undefined are individually stringified, they stringnified to their values null and undefined.
[00:04:08] But when they're in an array and they stringnified they just, they're magically empty like this third line here. You see there's a comma with two empty values on. Like that it's just insanity to me. It's distinct values they represent themselves when they're on an array they don't,
>> Kyle Simpson: Some of that last line, we have a slight little grammar nuance that arrays are allowed to have trailing commas.
[00:04:37] They don't imply an empty item in the end, so if you have a trailing comma there, that's why when we string a fight, there's only three commas because there really is only four empty values here, not five empty values. One array or I shouldn't say empty, they're actually undefined.
>> Kyle Simpson: ToString on objects however, by default ToString on objects will just create this bizarre kind of output where it says object capital O Object. The implication here is we have an object that was created with an Object function. It's not technically true that it's always created with that function, but it's a good representation that this thing was created.
[00:05:25] And I find this strange that no matter what the contents of the object are, we get the same stringification. So if I have an object with a:2 in it, I get no representation of that in the string output. I have to call JSON.string file on it or something like that.
[00:05:41] By the way, just as a slight little sidenote on that capital low object toward there. That is a specific term, that second word shows up there. A specific term In ES6 parlance, it's referred to as a string tag. In other words, it is the value that is used to describe an object when it's stringified.
[00:06:06] And by default, objects have a string tag called capital O Object. Because we're trying to imply that they were created by the capital object function, even if they weren't. Okay? But you can actually ask the vio6 overwrite that for your own objects. You can change the string tag for an object.
[00:06:25] There's a special metahook location called symbol that to string tag, and you can change it. So you can literally make your object present itself as lowercase o object foo bar or whatever else you wanted. So as an experiment, I said, what I'd really like my objects to do, is when stringified, I'd like them to show me the contents.
[00:06:45] So I changed my, for this one object in this experiment, I changed the string tag to be a getter that dispatches on the fly rather than a set value and it, JSON stringifies itself. So it actually print out lowercase object and then the JSON representation of the object.
[00:07:04] So you can do fun stuff like that once you learn about ES6 meta programming and things like that. More on that in my ES6 course and my ES6 and Beyond book.
>> Kyle Simpson: But two string by default is not very helpful for us on object types.
>> Kyle Simpson: What about ToNumber?
[00:07:23] This is one that's a bit more interesting, it's a bit more common for us and certainly references what we've been talking about with NaNs Many of these are pretty straightforward. Quote 0 gives us 0. Negative 0 actually does make a negative 0. So it works correctly in that direction.
[00:07:46] You'll notice the one where I have some white space and some leading zeroes. It just ignores any white space and leading zeroes, when it's calling so that's important because that's different than we try to call. Like parseInt on a function for example, we'll talk about parseInt later. ParseInt is doing something different than coercion, parse is trying to parse stuff and then it guesses if it sees a 0, thinks it octal or whatever.
[00:08:13] Here we're just doing straight up coercion. So a leading zero is no big deal at all, it's not gonna do anything special. That's why we end up with just the value 9 there from that fourth item. I find the the trailing decimal place and the leading decimal place, they're ignored, I find that interesting.
[00:08:34] But it's weird that if you leave off both the 0 on either side of it, you end up NaN. To me that seems strange, that is an inconsistency to me that it ought to produce 0 again. If it's gonna do it for 0 dot, and dot 0, it ought to do it for dot.
[00:08:48] And also you'll notice that if I do wanna do the base types, and I do the actual notation for it, like in this case, the hexadecimal syntax for the 0xaf. I'm gonna actually produce the base ten representation of it. But the first item in this list, the very first item at the top that I've highlighted there, that one I wanna give some special attention to for a moment.
[00:09:42] So, what about this empty string becoming the zero value, does that make any sense? Well, I don't know if any of you have ever tried it before, but this is somewhat unprecedented, because in virtually in every other language that I've been able to attempt this in, if you try to take the empty string and convert it, coerce it to a number you always get an error.
[00:10:05] C throws an error, Java throws an error, they all throw an error, because this is one of those strange corner cases and they chose in that particular case, and said look nope that's just an error, there's nothing we can do. There's no reason to assume that empty string means 0.
[00:10:19] Empty sting is the absence of some value, and 0 is definitely not the absence of a value it is an affirmative presence of a real important value. So to conflate empty string with 0 is troublesome. This turns out to be a really bad mistake made early on, and as a matter of fact you're not gonna know this yet, but you'll just have to trust me.
[00:11:12] I didn't believe it at first, but I kept trying and trying and it turned out, if you think logically about it, it turns out every one of them traces back to this one root cause. What should empty string have become? Anybody have any idea on a better value for it to become?
>> Speaker 2: Not a number.
>> Kyle Simpson: How about NaN? Doesn't that make more sense? If it had just become NaN instead of 0, virtually all other coercion woes would've gone away. But they chose to make it become 0, more on that in a little bit. Booleans, nulls and undefined, false becomes 0, true becomes 1, now I understand there's lot's of historical precedent for that.
[00:11:51] LIke in the C language, we didn't have Booleans so we used 0 and one interchangeably as true and false. But now in a modern programming language that does have the Boolean type, the fact that false becomes 0 is a little bit sketchy to me. The fact that I could say false plus true, and end up with the number 1 is strange to me.
[00:12:10] And I don't think it actually leads us to more reasonability in our code that we can just implicitly treat Booleans and numbers as interchangeable with each other. They have very distinct purposes, and I don't think we should treat them as that. So I would almost highlight false and true there, and say those becoming 0 and 1, respectively, is a strange decision, but I understand the historical precedent.
[00:12:34] So at least it's not out of left field, right? But null, becoming 0 where undefined becomes NaN? Now that is just straight up insane, right? That's just crazy. Why does null become 0, but undefined becomes NaN, just to mess with us? There's just no reason for this, this is a bad decision, one of those things I'd go back and fix.
>> Kyle Simpson: So what about when we call ToNumber on an object, an object or an array? Well it turns out that ToNumber when called against a non primitive, it first dispatches to another abstract operation called ToPrimitive. Because ToNumber can only operate on primitives. So we first have to take something that's not already a primitive, and make it into a primitive.
[00:13:23] And there's a two step process for turning something that's not a primitive into into a primitive. First, that ToPrimitive operation will dispatch to a valueOf and then it will dispatch to a toString. Now that's actually reversed when the end result is that you want a number versus the end result you want a string.
[00:13:43] If you were to coerce an object to a string it first goes to that ToPrimitive, and it calls toString first and then valueOf. But here we're trying to produce a number so it calls valueOf first, and if valueOf does not return us a primitive then it dispatches to toString.
[00:14:02] Now this is notable because you can actually create an object that has both a valueOf and a toString method to find on it. And you can make those methods return different answers, so you can really screw with people by creating an object that will coerce itself differently, depending upon whether you ask for a string or a number out of it.
[00:14:22] But not a good idea, but you can do that, okay? So primarily we think of the valueOf method as being the one that answers the question, what is the primitive representation of those objects. So imagine you've created a data structure, and you want that data structure, maybe that data structure is a point, right?
[00:14:42] Like a cartesian x, y, z point in 3D space. And you want that point when treated as a number to represent itself as some number. For example, you could have it be the Cartesian distance from that point in 3D space to the origin. So you could have it calculate internally, what is my distance, my Cartesian distance to the origin.
[00:15:07] And that's how you could represent that object as a number, have the valueOf function return that value, that number representation. Whereas when asked for string representation, you'd probably wanna print it as parenthesis and then the three x, y, z coordinates and then another parenthesis, right? So it does actually sometimes make sense to represent it very differently between the string and the number representation.
>> Kyle Simpson: So here's some interesting things, when we have the array that it's not an empty array, it's an array with an empty string, the first thing that happens is that the array needs to call its toString. So array toString produces the empty string, and then empty string produces 0.
[00:15:55] This is a two step process now. First we create the primitive, and then we do that. Two number on the primitive result. There is our root of evil coming back to bite us. If that empty string had produced a NaN then we would have resulted in a NaN.
>> Kyle Simpson: I find it very strange that both array of null and array of undefined produce 0s, whereas null and undefined by themselves produced different results. Here, because they're in an array and that array stringification is all busted up, they both produce the empty string, which then both produce 0s.
[00:16:36] Root of all evil thing again. Okay, so all these other weirdnesses are all coming from that one faithful decision. Same thing down there on that last one. [COUGH] Why do you suppose the array 1, 2, 3, becomes NaN?
>> Speaker 2: Two string first.
>> Kyle Simpson: It calls a two string which produces 1, 2, 3, and that string then coerced to a number, it sees the common, it's like, that's not a valid number.
[00:17:02] So it gives us NaN. All right, ToBoolean, does exactly what it sounds like, any time we ask for a value that's not a Boolean to be treated as if it was a Boolean. We're dispatching to this internal abstract operation called ToBoolean. Now I understand what ToBoolean does, we actually need to understand two concepts called falsy and truthy.
[00:17:26] And this sounds like a little bit made up. And it is sort of, these are terms of art. The words falsy and truthy don't actually appear on the spec, we don't really refer to things as falsy and truthy. It sounds like something a politician would make up. Well, it's sort of truthy-ish, kind of, right?
[00:17:44] What is truthy and falsy? Here's what we mean by that as the term right?. Falsy means if you have a value and you dispatch the tToBoolean operation on it, it will always produce false. So it is a falsy value if when coursed ToBoolean it produces false, okay? Examples of the falsy values, the empty string, any representation of the 0, null, NaN, false, and undefined.
[00:18:13] As a matter of fact, those are the only falsy values. There's a very specific list, it's literally this table in the spec that says these values when we call ToBoolean these ones produce the Boolean false. And it literally says everything else produces true. So there is no list of truthy values in the spec, something is a truthy value if it's not on the falsy value list.
[00:18:43] Examples of truthy values include "foo", the number 23, the object the array, the true the function, and there's a .., there because there's an infinite number of items on that list. So how do you know if something's truthy? If it's not on the falsy list. All you gonna dodo is memorize that falsy list.
[00:19:03] The string, the empty string, any 0, null, undefined, false and NaN, those are the falsy volumes. Now, something very important. If you're taking notes don't miss this really like highlight this and make sure you don't miss it. These rules only apply if legitimately, the ToBoolean operation is actually invoked.
>> Kyle Simpson: And that might sound like a no-op statement of mine. Well obviously, right? There are several places, which we're gonna look at in just a little bit. There are several places that appear syntactically as if they are producing a Boolean coercion, and they're not actually producing a Boolean, and therefore these rules don't apply.
[00:19:49] So the rules only apply if the rules apply. I sound like a politician, don't I? The rules only apply if it's actually being invoked. So it's important for you to know whether or not it's being invoked. Following me?
>> Kyle Simpson: So you have to learn the falsy list