[MUSIC] I want to continue emphasizing the importance of abstractions, signatures, and abstract types. By now showing you other structures that have the same signatures as the ones we've seen already, for our rational numbers. So a key purpose of abstraction, and of using signatures and abstract data types, is to allow different implementations of some functionality to be equivalent. What I mean by equivalent is, no client will ever be able to tell which one you are using. If you can guarantee via abstraction that clients can't tell, then you can take one implementation and replace it with another one. Maybe that one has additional functionality that doesn't change the old functionality, maybe it's faster. Maybe you just want to be able to delay your choice of which implementation to use until later, and have a guarantee that clients won't break when you make the change. This is easier to do, it is more likely that two structures are equivalent under a signature that reveals less, compared to a signature that reveals more. So I'm going to show you two examples of that, one in this segment and one in the next. The one in this segment is going to be a structure that, like our Rational1 structure, can have all three signatures we saw previously. It can have signature RATIONAL_A, RATIONAL_B, or RATIONAL_C. And it will be equivalent to the previous structure under RATIONAL_B or RATIONAL_C. It won't be equivalent, under RATIONAL_A. So let me tell you how this is different, it's a fun example using rational numbers, I'll show you the code in just a second. The idea is that structure Rational2, compared to structure Rational1, does not keep rationals in reduced form. It goes ahead and let's the numerator and the denominator to be something like 9 and 6. But then the toString function always reduces things, before it ever returns a string. Okay, so let me show you the code. Here it is, here is a structure Rational2, it's a lot like the structure Rational1. It has the same data type binding, the same exceptions. make_frac still raises an exception if y = 0, still make sure there's no 0 denominator, but it doesn't reduce the fraction. So if you call it with 4 and 2, it'll just return frac(4,2). Similarly, add never reduces anything either. You add two whole numbers, you get a whole number. For any of the fraction cases, we just multiply appropriately. In particular, frac(a,b) and frac(c,d) will just return (a*d + b*c, b*d). Not worrying that, for example, that two-thirds plus one-third, we would just end up saying, I believe, nine-ninths, okay? It's fine, we'll deal with it later. In particular, toString is now the only function that's using gcd and reduce. So I've gone ahead and made gcd and reduce local helper functions of toString. And all toString does, down here in the body of this let expression, is reduce its argument before it converts it to a string. And I would argue that under the properties or our specification, we're still doing everything correctly. We're not allowing denominators of 0, and when we return a string it is always in reduced form. And this structure, which is different in its implementation than our earlier structure, can have all three signatures we've seen previously. Here's our first signature, and it does provide all of these things at the correct type. Here is our second signature, it does provide all these things. If it provides everything in RATIONAL_A, it provides everything in RATIONAL_B. All we've done now, is made this type abstract. And in terms of RATIONAL_C, which is like RATIONAL_B, except that it has this function whole, of type int to rational? Our structure does provide that, because it has the same data type binding as our previous structure, okay. So now let's think about, if I add some program out there that was using Rational1. And I went into that program, and I replaced all the uses of Rational1 with Rational2, could the program behave any differently? And the answer is, it depends on which signature we give to those structures. So if we give both structures the signature RATIONAL_A, then clients might behave differently. And this is because RATIONAL_A, as we know, allows too much, it allows clients to use the Frac constructor to build their on fractions directly. So if you had some client out there, that called Rational1.toString(Rational1.Frac(9,6)), by violating the abstraction, it would get 9/6. But if you replace all the Rational1's with Rational2's, you'd get 3/2, because toString, in our new structure, reduces its argument. So if you have two modules that give different answers to the same argument, they're not equivalent, and you have to be much more careful while replacing one with the other. But the fascinating thing is, if you use RATIONAL_B or RATIONAL_C, there is no use of one module that leads to any different result than the same use of the other module. And that basically follows from the fact that the type rational is abstract. And given that the type is abstract, both modules enforce the same properties, and given the same arguments, always return the same result. So being able to replace one module with another one becomes easier when you expose less, in particular, when you make types abstract. And now we see an example of that. As one final point, you might be wondering if under Rational2, it's okay to go ahead and expose the Frac constructor, since toString is going to reduce things to reduced form? And the answer is no, this would still allow negative denominators or denominators of 0. So Rational2 is also incorrect if you expose the Frac constructor, for most of the reasons that Rational1 was. There's just a couple reasons that Rational2, as an accident of how it's implemented, now happens to handle correctly