Check out a free preview of the full Advanced Elm course:
The "Open & Closed Records" Lesson is part of the full, Advanced Elm course featured in this preview video. Here's what you'd learn in this lesson:

Closed records define the exact shape of a record, whereas open records specify a minimum set of fields that must be present. Richard shows how these work with constraint unification.

Get Unlimited Access Now

Transcript from the "Open & Closed Records" Lesson

>> Richard Feldman: Open and closed records. So this is a closed record, and the one below it is an open record. So a closed record essentially means this is exactly the shape of this record. It is closed for extension, you can't make any sort of variations on that. You also have open records, which look similar to closed records except they have this additional type variable with a pipe after it at the beginning.

[00:00:19] And that means that it's like a closed record except that it says I have at least these fields, possibly I have some more. So let's see an example. So here is a closed record, it says firstName = "Sam", and the type of these closed record is firstName : String.

[00:00:36] That's closed. Here is an open record. So if you say .username, just out of the blue you put that in the elm-repl, that will give you like a function that says I am an open record with at least a field called username. And I don't know what the type is, but whatever that record is, I'm gonna give you back one of those types.

[00:00:56] So you can call this .username on any closed record that has, or even another open record, something that has a username field. And whatever's on that field, that's what you're gonna get back. If we put this into elm-repl and we see, like, an anonymous function, take a record and then say record pipe name equals Li.

[00:01:15] That says, okay, based on what you've told me about this record, I can infer that it is at least a record that has a name field, and that name field is a string. So it's gonna say, I'm gonna take one of those, and then give you back another one.

[00:01:27] So this is the purpose of the type variable in an open record, is in case you have two of them appearing in the same type, it sort of unifies them and says well, I know that this record has to have a least name. And I'm gonna return you the same exact record that has to have that name.

[00:01:43] It's not just that I'm gonna return you any old record that has name, it's gonna be exactly the same shape as the one that you gave me. But all I know about them is that it at least has a field called name which has the type string.
>> Speaker 2: So this is declaring it right here with the arrow?

[00:01:59] Or-
>> Richard Feldman: That's just showing the type. So I'm pretending I put this into elm-repl, and elm-repl told me the type of this function, and this is the type of the function. So this is sort of an annotation for that anonymous function. I could have also written it out as a name function, but I'm not sure if that makes sense, though.

>> Speaker 2: Well, if I just wanted to create one of these-
>> Richard Feldman: Open records?
>> Speaker 2: Yeah, an open record.
>> Richard Feldman: [CROSSTALK] Right, so you can't actually just create an open record. So there are literals for closed records like we have up here. But the only way to obtain a type that has an open record is to use something like, either a function like this, or to modify an existing record.

[00:02:39] So they only exist in the context of type annotations, not really in terms of, and in terms of type unification, but not in terms of literals. So here's how constraint unification works in the context of open and closed records. So here we have two identical records, they're closed, they're identical, no problem.

[00:02:55] Here we have one that is open and one that is closed. So if I pass a closed record, that has x Int to a function that accepts an open record with at least x Int, it will unify as per usual, to the more constrained of the two, which is to say the closed record.

[00:03:10] And of course, if I give it two incompatible ones, which could be, by the way, that they have the same number of fields, and the same types on those fields, but different names for the fields. Even that is enough for them to be incompatible. So they either have to have exactly the same field names and exactly the same types on those fields.

[00:03:29] Or they have to have one is an open record that has at least the fields supported by that particular closed record. In that case, still unified to the more constrained of the two. So some examples of this, let's say we have an open record with a least x which is an Int, and with a closed record which has x which is an Int, and y which is an Int, it says great.

[00:03:47] No problem, I will unify that to the more constrained than the two which is to say the x and y closed record. We can also have two open records, one of which has more fields than the other. In which case, it will unify, once again, to the more constrained of the two, which is to say the one that has two fields instead of just one.

[00:04:07] Now, so we can get into some more interesting cases here with open and closed records. So let's say I write a function that says it takes a point and that returns point.x plus point.y. This is going to return something that says I'm an open record with a least x which is a number and y which is a number, and that's gong to return a number.

[00:04:24] So here we have open records interacting with our constrained type of number. And this will unify to x and y being a number, and worth noting that even though it's able to do this, even though I never at any point said I'm going to return a record that has both x and y.

[00:04:43] It has to infer that by unifying two disparate open records, one of which says I had x which is the number, the other which says I had y which us the number. One of the only things that makes open records different than all the other things we've talked about before is that this is an example of two things unifying which are not picking the more constrained of the two.

[00:05:04] But rather unifying their constraints, and saying this one has gotta have x which is the number, this one has gotta have y which is the number. I wanna put those together and say actually, both of them now need to have both x and y present in order to, so this is sort of like we've learned two different things about point from this one expression.

[00:05:21] One is that it has x and the other is that it has y, and it will unify those constraints and say, by putting all the knowledge I have about this things together I have learned that this record must at least have an x and it must also have a y.

>> Speaker 2: What you have here is of you wrote that in the REPL, that anonymous function, it would give you this type signature.
>> Richard Feldman: Yes, that's what it would infer just if you have no other information and just say here's my expression. That's what it would infer for you.