Record & Tuple: Immutable Data Structures in JS


Record & Tuple is a proposed feature for JavaScript that aims to introduce new immutable value types. Records are immutable key/value structures and Tuples are immutable sequences of data. In this talk, Robin, one of the champions of the proposal will introduce the feature, show how it can be used and explain how this feature is advancing in the TC39 standardization process.



Oh, that's a lot of people. Thank you for coming. I'm Robin. Today we're going to talk about record and tuple. And this is about new feature in JavaScript that hopefully you'll be able to use, I guess, soon. And it's hopefully going to redefine the way you handle data in JavaScript. So again, I'm Robin. I work at Bloomberg. I do JavaScript infrastructure. And so I work with thousands of developers in my company to improve their JavaScript experience. I'm also TC39 delegate for Bloomberg. That means that I get to participate in the standardization process of the JavaScript language. And also I'm one of the co-authors and co-champions of record and tuple. That's a good thing because today we're going to talk about them. Okay. What are we going to cover today? First we're not going to talk about record and tuple, but the objects and the types you know and love in JavaScript today. Then we're going to look at record and tuple. And finally, I'm going to tell you when and if you can use them. Okay. So let's start with objects. You already used them. And we're also going to talk about other types such as strings, numbers, very basic stuff. But nevertheless, it's important to take some time to look at them. We're also going to talk about how they're being referenced and how they're mutable in some ways. Let's start with primitives. And in this case, we have strings. On the first line, we create a string. On the second line, we create a second string. Or do we create a new one or is it the same string? That's the question. For JavaScript, when you put a primitive value in a variable and you basically use that variable somewhere else and compare it, if the value inside matches exactly the same characters, in the case of a string, it's going to tell you this is the same value. The same thing goes for numbers. For objects, well, on the first line here, I'm creating an object. On the second line, I'm creating a second object. Whether they have the same internal stuff in them is irrelevant here. At every line, I create a new reference to a new object and essentially we're going to point to another place in the memory. So we have two references to two different places. For JavaScript, those two references are not the same thing. They're completely unequal and they're not going to match. So I don't know if you tried to change one character in string, but if you try to do it, at least in strict mode, this is not going to work out. You're going to have an exception and you're not going to be able to mutate the internals of the string. But I'm pretty sure you've already done that with objects and changed the internals of an object by just setting something at a specific key. So here I'm just changing the name key and everything goes well. But some of you might know that you can freeze objects. That makes them basically immutable. So why would we need record intubal? We'll get to that later. But yeah, I can freeze my object and now I cannot change keys. So that's it. It's done. Mission accomplished. Except that it's nonrecursive. So if I want to actually mutate the array inside of that object, I can. Actually, that's a good thing here because I wanted to add my colleague Ashley to the champion group because he's been participating a lot on making record intubal amazing. Okay. So before we go further, some of you might know that mutating things sometimes is a slippery slope. And so if you know that this is a problem, this next part is going to be, yeah, duh. But it's still an interesting example here that I want to show you. So here let's say I get this init connections function from a library. I don't really trust it. But I need to use it. So I don't trust it to not change my config argument and I'm passing it down. And I don't know if that happened to you, but that happened to me once at least or twice or three times, many times actually. The config here, I don't want it to be changed because I'm going to use it on other places in my application, right? But init connections could very well change it. So what I'm going to do is freeze it, then I'm going to pass it to init connections and then I'm still not sure that my database key is not going to get changed. So here's an idea. I could JSON stringify it and then JSON parse it and then I get a copy and everything's fine, right? It's going to be pretty slow to do this and if I'm going to start doing this in a hot path, it's going to be a pretty bad idea for my performance. And if you were to do this, please use structure clone instead. It's available in most host environments. So your web browser node Deno has structure clone. Please use structure clone instead of stringify parse. Okay. Back to references. We had a mutability problem, but I want to talk about references just for a bit. They can be problematic sometimes. So here is an example that, hang on with me, many lines, but the first line creates a boat with a really nice name. The second line creates coordinates for that really nice name boat. And then I'm going to track all of the boats in the sea by using the boats map. It's an ES map available since ES6. And I can use my coordinate to map to the boats. Right? Simple. And if I want to look up the boat, I just give the coordinate to the map and I'm going to get back the boat. Except here's what I'm going to do next. I'm going to copy my coordinates for some reason. Don't ask me why. But if I'm trying to use my copied coordinates to look up the boats, I'm not going to get my boat. Because I get another reference, which is a reference to the copied coordinates. And how can we fix that? Let's stringify it. It's easy. I get a string. It's a value. It's going to match up in my map. Amazing. But this is a slippery slope. If I were to create my coordinates just the other way around and not inserting the keys in the right order, I would get this. An unequivalent string. So this is kind of a fragile approach. We really don't want to be doing this. And on top of this, you cannot put object cycles in there. Because JSON string, if I want, like it. And you cannot also put objects and values in there. So let's do a quick recap before we see a potential solution to this. So primitives, they are really cool. The variables hold values onto them, not references. So you can compare them easily. And you cannot mutate them. When it comes to object, they are held by reference, which is good. It's very important in the language that they behave like this. But in our specific case, it hasn't been like the best thing. And you can freeze them, but it's not super comprehensive. So what if we had a way to mush some values together and still treat it as a primitive type? And so that's why I conveniently introduced to you record and tuple. Yeah. Here's your first record. It looks very much like an object. But as you can see, there is this little hash symbol in front of it that makes it a record. And I'm not going to spoil it for you, but this is a tuple. Same thing. You have this little hash symbol that makes it from an array to a tuple. So just one character difference is going to make a pretty big change. Obviously, you can combine them, put records inside of tuples, tuples inside of records. They're immutable. By default, you create them, you put this hash character, you don't need to freeze it. It's just non-modifiable after it's been created. That's it. And that also goes deeply into the structure. So no problem with freezing recursively. We don't have to think about this. And that, I think, beyond the mutability, the equality semantics of those things are great. Because instead of comparing references here, if I have two tuples that have the same sequence of values in them, they will be equal. If I have two records that have the same pair of keys and values, they are going to also be equal. And we're going to use that. And that's because they are primitives. So if I type of them, I will actually get their own type of. They're not going to return object, they're going to return record and tuple. Okay. So let's solve our previous problems that we had with the objects, right? So about the freezing. We don't need to... Oh, sorry. We don't need to freeze anymore because it's completely immutable. If any connections try to change my config, it's just going to crash. So problem solved, I guess. When it comes to the second problem, well, we talked about equality with triple equal, but map will also follow the same kind of equality. That means that if my coordinates from an object becomes a record, now it's basically, instead of compare references to the coordinates, it's going to compare the internal values of the coordinates. So if I create a copy of exactly the same coordinates, since the values inside of the coordinates record are going to be the same, we're going to still be able to look up our boot finally. Okay. What about object cycles and unseries of all objects and values? I talked about that earlier. Well you won't be able to make them with record and tuple structures. We're going to get to that a bit later. And what about the key ordering fragility with the stringify that I showed you earlier? If I insert my keys in a different order, would that change anything? With records, no. Because records don't memorize the insertion order. They just lexically return the keys in the order that are lexically ordered. So if I take the two coordinates, if I insert my keys in a different way, it doesn't matter. Still the same values inside, still the same equality. Okay. There is no free lunch here. You cannot do some things with record and tuples, and for good reasons, and we're going to go through them. First of all, you cannot put an object in there. If you look in this slide, I forgot to put a hash in front of my db object here. It's an object now, it's not a record, and records don't like to have objects in them. Unfortunately that also goes for functions, because functions happen to be objects. There is still a way to fix that problem, and I'm not going to talk about it, because it's a solution that you will be able to use it, but I only have 20 minutes, I actually only have 9 minutes left, so I need to speed up. Okay. So now we're going to talk about how you can derive changes. Basically you can change things in place, right? You cannot push values in place to a tuple. How do you do this? Well, with arrays, you can concat things using the spread, right? You can do that with tuples as well, and as a matter of fact, you can do that with records as well. So if I just want to change one value in my proposal and create the future, I'm going to update it by spreading the proposal and changing the stage key here. We're not there yet, but soon. Okay. Beyond that, there are some really useful methods on the array prototype, right? For example, reverse. Unfortunately, it can't exist on tuple, because reverse changes arrays in place. We are introducing toReversed. toReversed is going to not change the tuple in place, it's just going to return you a new tuple that is effectively reversed. We have toReversed that is doing what reverse is doing. We also have toSorted that is doing what sort does, but not in place. We also have with. With is equivalent to doing an index assignment, except it's not doing it in place. And finally, we have toSplice that does whatever splice does, like don't ask me, and returns a new tuple. Those could be very useful in standard bug-down arrays, right? That's a good question. We added them in another proposal, change array by copy. It's a cool proposal. Again, a huge thanks to my colleague Ashley for pushing that to stage 3, but that means that I can write this beautiful function, sort by name. This function will work on arrays of records, or arrays or objects, or tuples of records, and this is going to sort my names that way. So my colleague Rick and Ashley were properly sorted, and then Rick Astley, I'm absolutely sorry about this, you're lucky I couldn't get the song because for IP reasons, they don't want you to play things, but I'm sure you have it in your head now, and that's sufficient for me. Okay. Let's summarize now. The syntax, pretty easy, you add the hash. It's immutable, so if you want to create derived updates, you need to do it by copy through either spread or the new methods that I showed you, and we also backported the methods to arrays, which means that you can write functions that will work for both normal objects, normal arrays, and records and tuples. We also have value equality instead of referential equality, and we have this working across all kinds of equality in the language, which is triple equal, so strict equality, map, and set. They cannot contain objects or functions, unfortunately, but stay tuned here. This is probably another complete other talk, but it's a very interesting rabbit hole, so stay tuned. Now you might be asking yourself the question, when can I use that? So record and tuple is a stage 2 proposal at TC39. What is TC39? TC39 is the standardization venue for JavaScript language. Actually, I should call it ECMAScript language. JavaScript is an Oracle trademark. Then comes stage 2. There are four stages in TC39, so stage 2 is like a bit in the middle, but essentially that means that the committee expects a feature to land in the language. So even if it might, and it will probably change what I just showed you today, it will ultimately still end up in the language. More info on this whole process with Heman's talk, I think it's on Monday on the online tracks. You can know much more about how TC39 works there. I would heartily recommend following this one. Okay, so what have we done to progress to the next stage? The proposal is being reviewed by multiple people. And so people in my company, but also outside in other companies as well. And my clicker, yeah. And we also have a Babel transform and a polyfill for it. Also we have spec tests that are being written. And we also... Oh, oh, oh. That's... Spoilers. No. Okay, we also have a Firefox implementation, but it's behind a compile flag. You cannot use it yet unless you compile Firefox yourself. Which you should try. It's really fun. And then when can you use the feature? For experimentation, you can try it now. Go on this address. Take pictures. Go on. And you go and you can try this in your web browser just right there. And then for your own projects, let's wait a little bit. Still stage two proposal. Let's not start using it in production anywhere. It's really not a good idea. What about the new array methods I just showed you? You can use them pretty much now. Because you have shims, you have polyfills, and it's in Safari tech preview 146. Which means that the next major version of Safari that should be around fall should be there. And we also... I didn't put it on the slide, but there is also a Firefox implementation behind a compile flag as well. And for existing production projects, just wait a little bit. I would wait for broad browser support for those features. Okay. I wanted to thank my colleagues from the record in Tuple Champion group before I leave you. Rick. Rick has been my main coauthor from the beginning. Day one, actually. And he made the playground that you will be able to try. Dan has been mentoring us. So he's been reviewing our early stuff, helping us with going through the process of TC39. And then Nicolo, Babel maintainer, now working at Igalia. He has been reviewing the spec, because obviously implementing spec in Babel, you know how to read spec afterwards. And he also did the first implementation in Firefox. And really impressive work. And then Ashley that joined us... I mean, I would have said recently, but it's been a while now. And he's been doing a lot of spec work. Fixing a lot of things. Finding edge cases. So huge thanks to him. And finally, I wanted to thank you all here at JS Nation. Also online. And here in Amsterdam. I'm Robin. I work at Bloomberg. You can find me on Twitter, you can find me on GitHub. And if you want to know more about what we're doing with JavaScript at Bloomberg, come talk to me as well. And you can also go to that website. And that's it for me. Thank you, thank you, thank you, Robin! And we have quite a few questions on Slido. Okay, let's go through them. Yeah. Okay, we have them on the screen. And let's start from the top one. Is there a way for us to cast objects or arrays into records and tuples? Yes, you can use the record and array constructors to just pass standard objects and standard arrays into it. Just a note here, if, for example, you're passing an array that contains objects, it's not going to recursively convert your objects to records. So you would still need to do that manually. We expect user-learned libraries here to step up and provide solutions. But that being said, yeah, it's possible. It's just going to be a shallow transformation. Yeah. Awesome. And a question from me personally. How do you agree in your team to call it tuples or tuples? American English versus British English. I say tuples, but tuples is absolutely correct. There has been a war, and we just chose to agree to disagree, and that's it. Cool, cool. This is how open source works. Let's take the next one. Does record... Oops. Could you please return the questions back to our screen? Dear AV team. Yeah, oh, thanks. Does record keys only accept string? Does it accept other immutable types, symbols? No, not symbols. We only accept strings. There are reasons for this. We can talk about it aside, but essentially we want only strings right now. Fair enough. And this question, actually, it came first. What other language libraries have been inspirations for records and tuples? Ooh, a lot of them. We kind of took a mishmash of everything. I used to deeble nebble in Clojure script a while ago. Got some inspiration there. Also immutable.js, obviously. We actually talked to Lee that created immutable.js, and we got feedback on this proposal. So I mean, it's not exactly the same thing, because, for example, immutable.js, or even like another functional programming language, will let you reference an immutable things inside of the immutable data structure. We chose not to, because it makes sense from the way the JavaScript language work. Every time you create a feature, there is, yes, a certain degree of inspiration you're going to take from something else. But at some point, you also, and especially with a very legacy language like JavaScript that has a lot of baggage behind, where you still need to maintain compatibility forever for all code, because you cannot break the web, right? So you still need to keep in mind how the language is working and kind of integrate nicely. As I've been showing, our goal also was not to create a completely new word with record and tuple that is completely different from objects and arrays and all of that stuff, but also create this interoperability layer, right? So essentially, yeah, I'm happy to discuss this also in the next ones. Yes, yes, yes. And let's take more questions in in-person mode. You'll find Robin in a special place where you can chat with him. And thank you very much. Great session. Thank you.
24 min
16 Jun, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic