You are on page 1of 4

, In this segment, we're going to introduce the struct construct in Racket, show you how it works, use it to come

up with a better implementation of our little arithmetic expression language, and then in the next segment we'll discuss why it's a better approach. So here's the new feature and how we're going to use it. You can have a special form that you write struct. Then the name of your struct. Then the names of the fields it's going to have. So here, I'm defining a foo struct. It's going to have fields bar, baz and quux. We'll see how to use this in just a second. It'll be obvious once I show you what functions are defined as a byproduct of creating this declaration, and I'll explain at the end of this segment what this transparent attribute is. And why I encourage using it. So when you have this struct declaration, a bunch of functions are added to the environment, once you evaluate this struct. The first thing you get, is you do get a function called foo, that takes three arguments, because this struct has three fields, evaluates those three arguments, and returns some new thing It's a foo. That has a bar field, a baz field, and a quux field holding the results of e1, e2, and e3. So in that sense a struct is like a record because it has these fields named bar, baz and quux. But it's more than that. Because we also gt a function foo question mark, that evaluates any expression at all in Racket, and returns true if and only if the result is something that was made from the foo function. So this is how we can take something and find out if it's a foo or not. And then we get Three functions for evaluating the fields, foo-bar, foo-baz, and foo-quux. So what foo-bar is, is it's just a function, just the name of the struct dash the name of the field, evaluates something. If that something, that result, was made with the foo function, the first thing I showed you, then you get the contents of the bar field. Otherwise, it's an error. If we call foo-bar on something that is not a foo, it's a runtime error. Similarly, foo-baz returns the baz field. Foo-quux returns the quux field. Okay? So it turns out these things can be used for idioms like our expression example. So what I'm going to show you in a minute, when I show you the interpreter. The new eval x function, is 4 struct definitions. For const, negate, add, and multiply. The

const needs 1 field I'll call int. Negate needs 1 field, which I'll call e for the subexpression. Add means 2 fields, e1 and e2. And multiply similarly for the subexpressions. So what we're doing with this collection of 4 struct definitions, is a lot like our ML data type binding. Because struct definitions are most like ML constructors. Not a data type binding. There is none of that when we're in a dynamically type language. But each of these is a lot like an m l constructor definition, which in m l was part of a data type definition. Because what you get when you define a struct, is you get a constructor, a function for making the thing. You get a tester, a function for finding out if you have one of the things. And you get extractor functions, data accessing functions for getting the various fields. So const would be a constructor, const? a tester, cost-int one of these extractor functions. So it's not pattern matching. This is a different approach. It's an approach I happen to like a little bit less but it works fine, it's efficient. And because we're in a dynamically-typed language, two things are not in this code that you see above. First of all, we never say anywhere in the language, these are all the kinds of expressions There are. That's for us to keep track of. And secondly we don't say what the types of teh fields are. There are ways to do that in racket, but this more dybnamically typed approach just says that any of contents can hold anything and it'll be up to us to make sure that multiply only holds expressions and const in it's field Only holds a number. So with that, let's look at the code. I have the struct definitions right here that I showed you on the slide. And, now we can write our e-mail x function. It's the same logic as we seen now in our previous implementation with list's. And with, NML. And that is, if you have a const using that const function that is part of the struct definition. Just return the entire struct, the entire const thing. If you have a negate, then use the negate e function that was created. Because we have a struct name negate with a field e on. This argument. The argument 2 eval x. So that's going to give back something when we call eval x with it. Hopefully, it will be a const. Because when I call const-int, that will get the underlying number, if the thing

was made from the const constructor. Otherwise, it's an error. I'll negate it. And then I'll call the const constructor to make a new thing. Similarly, with add. Take in the argument to eval exp. If it is made from add, then we'll do this branch of the cond. We will use the add-e1 function to get it. We'll call eval exp with it. We'll convert it to an int. Store that in v1. Do similarly for the second expression, so if you call add dot e to on e. Add dash e two is just a procedure that's built in and we get the second thing out. Now we have call const-int to get the number out. Let that be v two. Add v one and v two Put it back together by calling the const constructor. Multiply is the same as add. Multiply instead of adding. So, what we have when we run this, is we can really think of add as a procedure. Add? is a procedure. Add-e1 is a procedure. So if you said something like, define x to be add of constant 3 and constant 4, that will print as this tree we built, it's the result of the add constructor with the cons, with the 3 consectra constant 4. If I call eval exp on that, I get back the const 7. 'because, remember, we return an expression now. And this all works great. So it's a lot like our version with lists. Much more convenient to just have struct definitions without having to define everything. And it is actually different. As we'll exercise in the next segment the things made from these constructors are not lists they're something different. So this is the sort of approach we will take throughout the rest of this section. We'll use struct definitions and then in our eval x functions We will use all the functions that get automatically defined as soon as you have a struct. Okay, so let's just go back to the slides to finish up here. I just want to mention this attribute because I didn't explain this hash colon transparent. This is not a big deal. You could leave it off. And everything I've told you about structs is still true. But for us, if you don't have this attribute, the REPL will not print struct contents very nicely. So why don't I show that to you very quickly in the REPL. you'll see here that if I run the file, and say const 17. That prints as const 17. And, you know, if I had said const plus 3, 4. That prints as const 7. That makes sense. if I had some other struct definition foo, where I leave off the transparent attribute. Then if I say

foo plus 3 4, it just prints as it sum foo. It's more abstract. The rebel doesn't want to show it to us. I assure you that the bar field of Such a thing. If I said define y to be foo plus 3, 4. That even though, and I am missing parentheses here. ,, . That the y, just prints as foo. But if I said foo bar of y, the contents are, indeed, 7. So that's what the transparent is attribute is about. It does some other things for us that we won't get into here. There's also, by the way, a mutable attribute. So hash colon mutable, that you can put on a struct definition. If you do so, you get more functions. For each field, you now get a mutator function as well. So for example, for a struct card, that has a suit and a rank, like we saw in an earlier homework in the course, this would define a function, set card, suit bang. That would take two arguments, a card, and a value, and would update the card's suit field to be the new value. So whether you want mutation for your struct or not, is a decision for you to make. We've discussed in this course the advantages and disadvantages of having mutable data. There are a lot of disadvantages, but if mutable data is what you want, we use mutable data for things like promises, then the struct construct gives you that ability as probably will not surprise you in this course. All of the struct definitions we need in this section can be done better without mutation, and so we won't use this attribute. But it just shows you, that these attribute effect in some way, how the struct primitive in Racket behaves.