[MUSIC] It's now time to take our module example from the previous segment, and figure out a good signature to give it. And I think this will be more interesting than you might imagine. So from what we know so far, what would be natural is defined as signature, like you see on the slide, that hides the two helper functions that we don't want the outside world to know about. So we'd make no mention of GCD and reduce. But the outside world does need to know there's a type rational that could be wholes or fracs. That there is an exception BadFrac. There's a make_frac that takes a numerator and a denominator and returns a rational. Add, takes two rationals, returns the rational and to string takes a rational and returns a string. And this signature is something we can give to our structure. It will type-check. Then, the outside world will not be able to use GCD or reduce directly. That's okay. It's not a bad start. It turns out we made a crucial error. That is, by revealing the data type definition. This first line here where we told the outside world how rationale was implemented. Clients can violate all of our invariants and they'll be able to use the library in the way that will not lead to the results. Some of the behavior that we want. That we could include a comment or we can ask clients to please promise not to build their own rationals. To always call make_frac. Because make_frac checks for certain things and institutes our invariance when we get started. But I don't know about you. I have certainly found that when I put things in comments and documentation, my library clients don't always follow those rules. And it would be much better if my language had a way to enforce those rules. And it does. And I'll show that to you in just a second. But first let me emphasize what goes wrong here under this first signature RATIONAL_A. The key problem is the clients will be able to call the Frac constructor directly. They could make a frac out of 1 and 0, or 3 and -2, or 9 and 6. And these are all forms of values that the functions in my library assume do not exist. And once they do exist, all sorts of things can end up going wrong. So let me show some slightly different examples. I've already included everything here, exactly as you've seen it. So, I have this structure, Rational 1, that has this signature. You see right here. Okay. And now, let me just write some things that work and some things that don't work. So, suppose I wanted to add two rationals and suppose first I do this correctly and I call make_frac. So, one comma zero. And make_frac of say two thirds. Okay. If I do this, I get the exception BadFrac, which is the correct behavior. But if my client does not follow the rules and makes a frac directly, then it goes in an infinite loop, it turns out, and we could try to figure out why I think it's related to GCD, but we don't want to figure this out. We want to keep clients from doing something like this. All right. Here's something else they might do. If I just had a negative denominator, I think I end up overflowing. Again, because the arithmetic just assumed in the module there wouldn't be a negative denominator, and if there is, certain things are not behaving correctly and as a final, even simpler example, remember one of the things we promised clients is that we would always print everything in reduced form. But if they just call to string with a Frac directly, we're just going to get nine slash six. Because you may recall our two string code, which you see here, assumes it's argument was already reduced. And this is again something our client is able to violate. Okay? So, this is what we want to try to prevent. And here's the intuition. The intuition is that an ADT should hide the concrete representation of a type. That way clients would never be able to make anything of the type. Without going through our functions, like make_frac. That way we can get those invariants installed and then our functions can keep them. So, here's how you might think to do this. Let's just take the signature we have before and take out the data type definition. Don't tell clients that it can be built from a whole constructor or a frac\g constructor. So, this does not word here, and the reason is the type checker sees these types rational and says I've never heard of such a thing. RIght? It'll just give an error that says you can't just make up type names like that. I need to know there's a type rational, otherwise I think you just had a typo. Al right, so that's good the Type Checker is helping us. Somehow, what we want to do is tell the Type Checker that for this signature, yes rational is a type but no I don't want clients to know anything more about it. And that is an absolutely crucial idea. Which is known as an abstract type. You can know the type exists but you can't know it's definition. So, this is how we do this in ML. This is a feature provided by ML, which is in signatures. You can just write type and the name of a type. And if you have no equals and no more information, it means what I just said. The type exists, but the outside world can't know what it is. So, here is a signature I like very much. I'll call it RATIONAL_B. And it tells clients what they can know about rationals. It says you can know there's a type rational. You can know there's an exception BadFrac you can know that make_frac returns a rational given two ints. Add can take two rationals and return a rational. ToString can take a rational and return a string. And if we give rational one, this signature, we will still be able to try all the examples that use make_frac correctly. But the outside world no longer knows there is a Frac constructor. Capital F r a c. And so, it won't be able to create any of those values that violate our invariance. So, this is a really big deal. There is nothing a client can do now to violate our invariance. We could take the structure, we studied carefully in the previous segment. And this signature and convince ourselves that all of our properties will always hold. Here's the intuition of th argument. How are we going to make it rational. The first rational a client ever makes has to be made with because this is all they have to create rationals. You can't call add until you already have a rational. You can't call toString until you have a rational. So, you're going to have to start with make_frac. And we could study the code for make_frac and convince ourselves that it gets all the invariance and properties correctly. No zero denominator, no negative denominator, fraction in reduced form. After that, the only thing you can do with rationals is add them together and convert them to strings. And we would similarly convince ourselves that those functions were implemented correctly. Now to the outside world, it can do what it wants with rational values. It can put them in lists, it can pass them to functions, it can put in tuples, but the only operations it can perform that access the pieces are those provided in our library. Now the reason why our structure actually has the signature is because it does define everything. Defines make_frac, add, toString can have these types. And it does define a type rational. It does it with a data type binding and it is a perfectly good way to define a type. The outside world just doesn't know that it did it with a data type binding and it certainly doesn't know the details of that data type binding. This is how you use signatures with abstract types to properly in force abstractions and implement data types. So, what we have now are two powerful ways to use a signature to hide things from clients. The first one is the deny bindings exist. That if you leave val-bindings, fun-bindings, constructors, and so on out of your signature they simply don't exist to clients. But the second more sophisticated and more exciting way to hide things is to take a type definition, tell the outside world that yes, you have defined this type, but I'm not going to tell you how I did. And that's important so that we can say that make_frac returns a rational, add takes two rationals and returns a rational, without revealing what the rational actually is. We'll see some other things that signatures can hide in one of the later segments on modules. But these are the two things I hope you'll always remember. Now, before we finish up this segment, I want to show you a third signature. It's also in the code file that's posted with these new materials. And this is just a little bit cute. So it turns out that if you look at the data type binding for this module, which I have right here. It was a problem for our invariant to export the Frac constructor and I showed you a bunch of examples of that. But it turns out it would be fine to export the whole constructor. Then our library doesn't mind any int passed to whole. So you can't get in trouble that has to do with our particular properties and invariants but it turns out you can convince yourself it would be okay if clients used whole directive. Okay so we actually can export it, and we can do it with this signature. It turns out we could go ahead and tell clients that there is a function Whole with a capital W of type int arrow rational. And if you take this structure as we've already defined it and type second against this signature ML allows it. And that's kind of surprising, perhaps, and the reason why is when it sees this data type binding, it remembers all the way back from when we first learned data types. That this defines a number of things. It defines a type rational yeah yes, but also a function whole of type int or a rational, Frac of int*int rational, as well as whole and Frac being allowed to be used in patterns. Signatures let us hide somethings and reveal somethings. And in this particular example, ml will allow us to expose that there is a Function Whole of type int -> rational. There is a type rational, but still hide all the other things that the data type binding gave us. There's a bit of a peculiarity to ML. I don't think this is the most important feature in a language. But I do find it cute, and I find it that it emphasizes that signatures get to expose some things and hide other things, and there's just a particular way we could let clients do a little bit more. They can call Whole directly rather than having to call make_frac with a second argument of int.