Sunteți pe pagina 1din 8

[music] In this segment, we're going to make the same additions to our Ruby code that we just made

to our ML code. And we're going to end up needing to learn an OOP programming trick called double dispatch. So remember the idea is that we want to add strings and rationals to our little language. The most interesting thing is that we change addition to work on any combination of strings and rationals. So end up with 9 different cases, because we have an int, string, or rational, as the left operand, and int, string, and rational as the right operand. We have 9 different ways that we might need to add, when we have these two values. And our language values are now Ints, or Strings, or Rationals. So, in the ML code, we ended up with a helper function, add values, where we used nested pattern matching to have those 9 cases. In OOP, it's not going to work out so well. Okay. So let's switch over to the code and do the easy parts first. Let's just add a string class and a rational class. It turns out Ruby has a standard library of classes called string and rational. So I'm going to call my classes MyString and MyRational. But in both cases, they are sub-classes of value just like int is a subclass of value these are the things that eval in our language might return. And for MyString, most of it is pretty easy, you're going to have an instance variable s that actually holds a string. Okay. The way, MyString evaluates itself. It just returns the entire thing. That's what values always do. Tostring just return the underlying string. Haszero, noNegConstants, and so on. And each of these method definitions corresponds exactly to some line in the ML code. That was a case of a function. One of the branches of a pattern. And here, there are method. This is all old news. Similarly, MyRational works the same way. We have to add the same methods.

It's a little more sophisticated to convert a rational to a string. Particularly noNeg constants continues off the screen a bit because I remove negative constants from both the numerator and the denominator. But adding these methods is exactly what we learned previously on how to extend an OOP program to support new kinds of data. The interesting thing, what this segment is all about is how to implement additions eval method. Okay? So, this is exactly where, in our ML program, we call the helper function add_values. With the results of evaluating e1. And the result of evaluating two, the two sub expressions. And we're going to do the same thing here but in OOP style we never call a helper function. We say well these things should know how to add themselves. We'll just send a message its called a method. So what I'm doing is I'm recursively calling e1.eval. That will get me back some value, either an int, a myRational or a myString. What I'm going to do is then call its add_values method with e2.eval. So that will pass it the other value and then they need to add themselves together. So what I need to do is go back to int, MyString and MyRational and add an add_values method. If it were that easy, this would be a pretty natural OOP thing to do but it's about to get more difficult. Let's go back to the slides, to see why. So here's where we are, it all starts promising. We're going to take an OP style where the way we implement eval and add, is we call the add_values method. On e1.eval, which will be some value. With e2.eval, which will be the other value. Those are 2 operand to add_values. So now, we have to implement these add_values methods. So I'm going to go to int, MyString and MyRational, and add an add_values method. So let's just focus on how int would do this. So def, add_values, take some other v. And it has to implement how to add itself to v. And the problem is, what it needs to do

depends on what kind of thing v is. The way we add to an int v is different than how we add to a string v. So there is a solution to this. That is probably the first thing you would think of. And I will show this to you. But you're not allowed to do it on your homework. And the reason is it's too easy, its not OOP enough. I'm going to show you here that its not OOP. And if you want OOP I will give you OOP. Right, this is the OOP version of the course. So here's what you could do and it would work. We could ask is_a questions. We could take v and we say, well, v.is_a int. If it is an int, then I'll have a branch where I'm an int. It's an int. Let's add the two ints together and return a new int. Otherwise if it's is_a MyRational? Well, then I make a new MyRational well, or I multiply myself by the denominator and do the right arithmetic. Otherwise, let's assume that the only other possibility is MyString so we won't ask another is a question and we just do the correct concatenation of two strings. So I don't mind this so much as a programming style. I just reject the idea that it's object oriented programming, okay? So what we've done here is that we had a hybrid that's half OOP and half functional. So here I have 3 of my 9 cases. I'm going to have another three in the add_values method of MyRational and another three in the add_values method of MyString. So what have we done? We picked which add_values method to call using dynamic dispatch. That's what we did right here. When add said e1.eval.add_values, which of the three methods we call depends on is e1.eval an int, a MyString, or a MyRational. That's OOP. But then in those methods, we basically switch to Racket-style programming, right? We basically switch to a cond statement where we ask the type of the other thing. No types in Ruby, the, the class of the

other thing. And that's where it's a weird hybrid. That we decided to do half of it OOP, and then switch the other way. And I'd prefer you either just have your nine cases together, like we did in the ML code, or you do full OOP. And getting full OOP to work here is going to cause you to have to scratch your head a bit. But we can do it. I'm going to show you exactly how it works in this example. So here's the idea of where we're going. That in that add_values method in int. The, we needed to know what kind of thing v is. To do the addition, we had to know is v an int, a MyRational, or a MyString? So, in OOP, you're never supposed to ask that question. You're always supposed to do the same thing instead. Instead of asking what kind of thing v is, call a method on v, and have different kinds of things implement that method differently. So, we are going to call a method on v and have it do the addition. Now what we can't do is just say v.add_values passing ourself. Because then we're going to have the same problem. It's going to call us. We're going to call it. We're going to go in an infinite loop. No. We need to call a method on v and tell it what kind of thing we are. And that is something we know. We know that in the int method, in the method in Int self is in Int. The method in MyRational, its a MyRational and so on. And we're going to tell v by calling different methods on v based on what kind of thing we are. And this is a programming trick called double dispatch and how about next I'll just show you the code. This is not something that I would expect you to come up with on your own but it does work. So we're going to add to our three value classes int, MyRational and MyString, add_values and three other methods. So what you do in add_values, this is in class int, I'm in class Int right now. Is add_values of v. Call's v's addInt method with self.

Alright? So what I'm saying to v, is you need to know how to add yourself to an int. And then do so with me. Alright? So you need to know how to add yourself to an int. Do so with me. Let's look at what the MyString class does in add_values. It call v.addstring with self. It says v, you better know how to add yourself to a string, now do so with me. And in MyRational, we're going to have add_values call v.addRational with self. So what we've done here is the call add_values that was done in the class add was the first dispatch, that broke it down into you know, width, heading on the first operand. And now we're calling three different methods. Myrational calls addRational, MyString calls addString and int calls addInt on v. So, as long as all of our values implement addInt, addRational, and addString, we'll be fine. And if three classes all implement the same three methods. That is 9 total method definitions. And that is where our 9 cases for addition go in a fully OOP style. So now, let's see how that works. I'm back up here in class int now. And I need to implement addInt, addString, and addRational. So now, what this definition, this method does, is, I know how to add myself to an int v. See, addInt is only ever called with int objects. In fact, the only place we ever call addInt explicitly is right here. Okay? So. If I'm an int, it's an int. We're both ints. Int.new, v.i plus i. That's where this case belongs. In class int, the way you add yourself to a string is v.s plus i.to_s. Now, the order here may seem flipped to you. That is because the add class called add_values on the left operand, then this would have gotten called on the right operand, so self belongs on the right. And that's why the order matters here, because converting to strings is not commutative.

Finally, ints addRational takes in a v. Which we know is a rational. And it creates a new rational dealing with the appropriate arithmetic with myself, and v's denominator, and so on. So that is the implementation in class int. In string, right? Strings know how to add themselves if the other operand is an int by converting that int to a string and then concatenating on ourselves. If the other thing is a string just concatenate the two strings. If the other thing is the rational do the appropriate concatenation. Again, we're writing out the same 9 cases we had an ML. We're just doing it in 9 different methods, right? Each of int MyRational MyString has an addInt, an addString, and an addRational. I've shown you 6 of those for MyRational. Here is the addInt, here is the addString, here is the addRational. So I have shown you all 9 methods. So now we have our 9 methods, those are the 9 cases. How is it that we're picking the right one? We're picking a right one in a very OOP way. What we did, I'm back here in class add now, is eval, did the first half of the picking. It called e1.eval.add_values. And depending on what the class of e1.eval, that's going to pick one of the three add_values methods we defined. There's an add_values in int, in MyString, and in MyRational. Then, those three methods picked the second half of it, and picked the right one of the 9 methods we implemented, by calling either addInt, addRational, or addString, and passing self. And that is double dispatch. Okay? So, here's a slide summarizing things. Remember, we have these 3 classes that each define the same 3 methods. So we have 9 total methods, one for each case. And now, we just need to use dynamic dispatch to pick the right one. So, when adds eval method calls e1.eval.add_values of e2.eval, that will dispatch to either Int's, add_values, MyString's add_values or MyRational's add_values.

Pardon me. What those methods do, they always taken in an argument v. An int calls v.int at int of self. That's going to pick 1 of our 9 choices. Or, if MyString calls v.addString with self. That will pick a different of our 9 choices. And MyRational will call v.addRational of self. And that will pick the right one of our 9 choices. And so we did things in two steps. And that's why it's called double dispatch. If you want to see a language that can do this all in one step, watch the next optional segment. But in most object-oriented programming languages, you have to do this trick if you really want to program up a binary operation like our addition in an object-oriented programming style. So, that's what you'll need to do on your homework, port ML code to Ruby code. We give you lots of pointers to get you set in the right direction. And you might be wondering why on earth am I showing you how to program like this. Well, to be honest partly it's to belittle. Peoples full commitment to OOP. I don't find this particularly natural or a simple way to program up a simple thing. I think the ML code is simple. I think you should program like this with 9 easy to read cases all together, but if you want OOP this is how you get OOP. There's more to it than that. I also want you understand dynamic dispatch, understand the semantics of method look up. And one way to really force you to understand that is to make you use a sophisticated idiom, where you really have to think about how the dynamic dispatch is working. I think it makes for a nice homework, and it contrasts with the next segment where I show you how a different language construct could make this much easier. And finally I do have the Java code, that does the corresponding thing. That is optional. But I do find it interesting particularly because the types make it a bit clearer what's going on. So in Java, you have to say that your class value, which is what int rational

and string extend, has these methods, okay? And what add values does, is it takes another value and returns a value. Whereas as addInt, addString, and addRational know more about the type of their argument. They know the other argument is an int or is a MyString or is a MyRational. I could call it rational in Java. And so if you do know Java, you may find this idiom a bit easier to understand by working through the Java code. Which is exactly like the ruby code once you get down into the classes but has these additional type declarations to help explain whats going on. And that is double dispatch.

S-ar putea să vă placă și