Advanced Elm Open Records
Transcript from the "Open Records" Lesson
>> Richard Feldman: So why do these exist? [LAUGH] Why have a distinction between open and closed records? Well we've seen quite a few examples and one way you could think of it is, well how would you do any of those examples otherwise? But it actually really boils down to one thing, which is essentially based on the following example.
[00:00:17] Let's say I have a type alias Model. It's got a name which is a String, age which is an Int, and then Post which is a list of posts. And in comparison, I have type Model = Model String Int (List Post). Now, we don't need a notion of records at all if we have custom types, as we can see here.
[00:00:37] I mean, really this Model's the same information, right? In both cases, I've got one value, named Model. And it holds on to, a string and Int, and a list of posts. Now granted, its nice to be able to tag these with names, like to say, this is a name, this is age, this is post.
[00:00:55] Especially because, what if several of them, like most of those, multiple version of them were strings. I don't wanna have model, string, string, string Int. That's gonna be very confusing, very error prone. So records, you know, make things less error prone. There's clearly a use case. But, strictly speaking they don't need to exist.
[00:01:11] And in fact there are plenty of ML family languages like Elm, which either don't have a record system or have a record system that's not nearly as flexible as the one that Elm has. So why bother having them at all? Essentially it's for this, and only this. The ability to say record.name.
[00:01:28] It's that dot syntax being able to say, I know I've got a record. I wanna access this field on there and be certain that it exists. If you want to have that and you want to have type inference, it's really hard to do that without something like open records.
[00:01:41] So that's the whole reason they exist. And I mention this because there are a lot of other things that they can be used for, despite the fact that all they're intended to be used for is literally this. And this relevant, or potentially relevant, because of the implications that this might have for the future of the language, which we'll get in to in a bit.
[00:02:01] Okay, so here's one of the things that they happen to be able to be used for is naming arguments. I talked about this is Elm Europe 2017. So let's say I have a function called validate, and it takes email, firstName, and lastName, and it returns blah, I don't know, some validation result.
[00:02:16] Email, firstName, and lastName in this example are all strings, so anytime I see this as a type signature, alarm bells go off in my head. I'm like, okay, I'm gonna definitely mix those up at some point in my life and make a mistake. One way to solve this is, by using records as a means of creating sort of pseudo named arguments, or faux named arguments.
[00:02:38] Some languages have first class support for, that's like a feature. It's named arguments. But, we can do the same thing in Elm using records if we want. So I'm just saying, validate now takes email, which is a string first name, which is a string last name, which is a string and then I just destructure them immediately.
[00:02:52] So as if I had, I end up with the same things in scope as with the previous version. But now, the caller has to specify here is the email, here is the first name, here is the last name. So it's no longer as error prone, but now it doesn't support partial application anymore.
[00:03:06] So that's a trade-off. It's a reasonable choice to make either way. But there's also one more way I can do it, which is I can do the same thing as the previous example, except I can make this an open record. And now, this means that I could potentially do something like, if my model happens to have a field called email, and a field called firstName, and a field called lastName.
[00:03:25] I can just say hey, validate model, and it would only use those three Fields from the model which is in a lot of ways better than taking the entire model as an argument, and it'll still work the same way as the others did. So again, not something it was designed to be used for, but as it turns out it is something we can use it for.
>> Richard Feldman: Another thing we can potentially do to solve this same problem, is to make one of them a custom type. So let's say that instead of having email be a string we said, you know what? Email is a separate notion of a string. I'm just gonna make a custom type called Email.
[00:04:00] Might even not bother making it opaque. And just say, look, if we have an email, that's a different thing from having a string. An email is just conceptually different, it's a special type of string. The second function I would argue is not really error prone. So it's taking Email, firstName, and lastName.
[00:04:16] Now granted, that is still taking two string arguments right in a row, but passing last name and first name is not really a mistake I don't think I've ever made in my career. I always pass the first name and then last name. I could maybe get paranoid and do it even more so than this, maybe use the record.
[00:04:30] I don't think I would go as far as making a custom type for firstName and a custom type for lastName. But you could if you were really concerned about it, but I think this is a sort of a more lightweight way of solving the same problem. Especially because email is likely to come up in a lot of different places, and having a custom type for email is something that's gonna pay off beyond just this function.
[00:04:49] And, in fact, in this code base I specifically did do this. I actually made a custom type for emails so I can disambiguate when I have an email versus I have some meaningless string, or some sttring that I'm not as concerned about.
>> Richard Feldman: So this is the way that you could do it with an opaque type, or even just a custom type that doesn't happen to be opaque.
[00:05:07] And finally, the other thing that you can use open records for is for modeling data. This is another unintended use for them. So there is two ways to do the following thing. One was with the type parameter, the other was with open record. So we look at the open record way first.
[00:05:22] So you say type alias Article a = a pipe title, which is a string, and tags which is a list of strings. So this comes up in the following scenario. We have a feed full of articles that we load. And in that feed we'll have, I don't know, maybe it's like 10, 20 articles something like that, and each of them just shows a preview of the article.
[00:05:40] It has like a title and then the author, and like a little blurb about it and a link to read more. Now also, sometimes articles have a body that goes with them, the entire text of the article. And when we're reviewing the article's page, like where we click the link to read more we view the entire article.
[00:05:55] We want all this info, like the title, the tags, and all of that, but we also want the body. So essentially we want two variations on article, we wanna have one, which is a preview of an article, and then the other which is the full article including the body.
[00:06:07] Now when we're loading the feed, we're not gonna get from the server the entire text of all 20 articles, that would be way too much data to transfer cuz we're not gonna bother using it. So the server's only gonna send us the preview information. But when we go to the full article, we do want the body, we want all the other stuff, plus we want the body.
[00:06:22] So how can we say I wanna have an article that sort of shares these two pieces of common information about the metadata of the article, but which separately has in some cases a body, and in some cases does not? A pretty common way that people reach for to do this when they realize that open records are a thing, is this.
[00:06:40] Because it's available, and it works for that use case. You can also do it, though, with a type parameter. You can say, type alias Article a =, and just add that type parameter on the end. And essentially say, okay, I've got some sort of article here, that has some particular piece of extra information associated with it.
[00:07:01] So the problem over here is sort of like what type would we extend this with? If we're doing it using an open record, that type must be a record, whereas if we're just adding it as a type parameter, we can have it be whatever we want in whatever field we want.
[00:07:15] Could be a custom type, could be anything.