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.
Record & Tuple: Immutable Data Structures in JS

AI Generated Video Summary
Today's Talk introduces the concept of record and tuple in JavaScript, which provide a new way to handle data. The Talk explores the references and mutability of objects, strings, and numbers in JavaScript. It discusses the limitations of freezing objects and proposes record and tuple as a solution. The Talk also covers methods and syntax for tuples, the status of the proposal, and acknowledges the contributions of various individuals. Overall, the Talk aims to redefine how data is handled in JavaScript and encourages waiting for broader browser support before implementing these features.
1. Introduction to Record and Tuple in JavaScript
Today, we're going to talk about record and tuple, a new feature in JavaScript that will redefine the way you handle data. We'll cover objects, strings, and numbers, and discuss their references and mutability. Let's start with strings.
And join us at the React Jam. Here are a few of the things you'll find out. Thanks. So let's begin.
So 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 we're coming to you with on the next event. And we're going to look at how we can improve it. 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 coauthors and cochampions 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.
2. Understanding JavaScript References and Mutability
In JavaScript, primitive values like strings and numbers are compared based on their exact characters. Objects, on the other hand, are compared based on their references in memory. Changing a character in a string or mutating the internals of an object is possible, but objects can be frozen to make them immutable. However, freezing is non-recursive, so arrays inside objects can still be mutated. This can be useful when adding or modifying keys in an object. Mutating things can be tricky, so let's explore an example using the initConnections function from a library.
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, 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 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 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 can change keys. So that's it. Just done. Mission accomplished. Except that it's non-recursive. 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 sleep free 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 initConnections function from a library.
3. Freezing and Copying Config Arguments
I don't trust my config argument to not be changed by initConnections, so I freeze it and pass it as a copy using JSON stringify and parse. However, this can be slow and impact performance. Instead, use Structure Clone, available in most host environments.
I don't really trust it. But I need to use it. So I don't trust it to not change my config argument 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 initConnections could very well change it. So what I'm going to do is freeze it, then I'm going to pass it to initConnections, 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, Deno, has Structure Clone, please use Structure Clone instead of Stringify Parse.
4. Understanding References and Potential Solutions
Let's talk about references and how they can be problematic. I'll give you an example using boats and coordinates. When using a map to track boats, copying coordinates can lead to issues with references. One way to fix this is by stringifying the coordinates, but this approach is fragile and has limitations. Let's recap before discussing a potential solution.
Back to references. We had an immutability problem, but I want to talk about references just for a bit. Here is an example, 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 boat's map. It's an ES map available since ES 6 and I can use my coordinate to map to the boat. 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 is 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 boat, I'm not going to get my boat because I get another reference which is the reference to the copied coordinates. And... how can we fix that? Let's stringify it. It's easy. I get the 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 won't 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.
5. Introduction to Record and Tuple
Primitives hold values, not references, and cannot be mutated. Objects, held by reference, can be frozen but have limitations. Introducing record and tuple, which can mush values together and be treated as primitive types. They are immutable, non-modifiable, and have great equality semantics. Solving previous problems with objects and freezing.
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, right? 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 introduce to you record and tuple. Yeah. Here is 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 are 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 immutability, 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.
6. Immutable Records and Deriving Changes
We don't need to freeze anymore because it's completely immutable. Map will compare the internal values of the coordinates. Records don't memorize the insertion order. You cannot put an object or a function in records. Deriving changes in tuples and records can be done using the spread operator.
Well, we don't need to, oh, sorry. We don't need to freeze anymore, because it's completely immutable. Infinite 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 of 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. What about object cycles and unserviceable 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 I showed to 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.
There is though no free lunch here. You cannot do some things with records 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 nine minutes left, so I need to speed up.
Okay, so now we're going to talk about how you can derive changes. Obviously you cannot change things in place, you cannot push values in place to a tuple. How do you do this? Well, with arrays, you can concat things using the spread. 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.
7. Tuple Methods and Syntax
Unfortunately, tuples cannot be reversed in place. Introducing toReversed, toSorted, with, and toSplice methods for tuples. These methods allow for non-destructive operations like reversing, sorting, index assignment, and splicing. They are also available for arrays through the change array by copy proposal. The syntax is easy, just add the hash. Records and tuples have value equality and the methods have been backported to arrays. However, objects and functions cannot be included. Stay tuned for more on this topic.
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 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.
So, 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 three. But that means 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. 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. 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.
8. Introduction to Record and Tuple Proposal
Record and tuple is a stage 2 proposal at TC39, the standardization venue for the JavaScript language. The proposal is being reviewed and has a Babel transform, polyfill, and spec tests. There's also a Firefox implementation behind a compile flag. For experimentation, you can try it now, but for your own projects, let's wait a little bit.
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 the JavaScript language. Actually, I should call it ECMAScript language. JavaScript is an Oracle trademark.
Then comes stage 2. There are 4 stages in TC39. So, stage 2 is 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 Himan's talk, I think it's on Monday on the online tracks. If 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. Well, that's... Spoilers. No... Okay. We also have a Firefox implementation, but it is 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 a feature? For experimentation, you can try it now. Go on this address. Take pictures. 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.
9. Status of Proposal and Browser Support
Still stage 2 proposal. Let's not start choosing it in production anywhere. It's really not a good idea. What about the new array methods that 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 for... It 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 a broad browser support for those features.
Still stage 2 proposal. Let's not start choosing it in production anywhere. It's really not a good idea. What about the new array methods that 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 for... It 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 a broad browser support for those features.
10. Acknowledgements and Conclusion
I wanted to thank my colleagues from the record in Drupal Champion group. Rick, my main co-author, made the Playground. Dan has been mentoring us. Nicolo, Babel maintainer, reviewed the spec and did the first implementation in Firefox. Ashley has been doing spec work. Thanks to everyone at JS Nation and in Amsterdam. I'm Robin, you can find me on Twitter and GitHub. Learn more about JavaScript at Bloomberg on our website.
Okay. I wanted to thank my colleagues from the record in Drupal Champion group before I leave you. Rick. Rick has been my main co-author from the beginning. Day one, actually. And he made the Playground that you will be able to try. Dan, that 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 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.
QnA: Q&A on Casting, Pronunciation, and Inspirations
Is there a way to cast objects or arrays into records and tuples? Yes, you can use the record and array constructors. However, if you're passing an array that contains objects, it won't recursively convert them to records. The team has chosen to agree to disagree on the pronunciation of tuples. Record keys only accept strings, not symbols. Many other language libraries have inspired records and tuples.
And we have quite a few questions on Slido. Okay, let's go through them. Yeah, 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 LAN 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.
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 like 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 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 came first. What other language libraries have been inspirations for records and tuples? A lot of them.
QnA: Record Keys and Language Inspirations
Record keys only accept strings, not symbols. Many language libraries have inspired records and tuples, including Closure scripts and ImmutableJS. While other languages allow referencing non-mutable things within immutable data structures, we chose not to for compatibility with JavaScript. Our goal is to create an interoperability layer with records and tuples, integrating nicely with existing language features.
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 came first. What other language libraries have been inspirations for records and tuples? A lot of them. We kind of took a mishmash of everything. I used to de-bull-nebble in Closure scripts a while ago. Got some inspiration there. Also, ImmutableJS, obviously. We actually talked to Lee that's created ImmutableJS, and we got feedback on this proposal. So, I mean, it's not exactly the same thing because, for example, ImmutableJS, or even another functional programming language, will let you reference non-mutable 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, 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.
Comments