[MUSIC] In this segment, I'm going to show you a third

implementation of rational numbers that will be equivalent to our other two, as

long as we keep the type abstract. And what I'm going to do here, is

actually change the implementation of the type rational,

in order to emphasize that when the type is abstract, an equivalent structure can

go ahead and change how the type is implemented.

So, given a signature that has an abstract type, different structures can

have that signature while implementing the type in different ways.

Those structures might or might, might not be equivalent.

I'll show you an example where they are. So I'm about to show you a third

implementation, rational 3 And in rational 3, the type rational is going to

be implemented as a type synonym with int * int.

So let's go over and see that. Here is my structure.

You'll see I have a type synonym here, so in the rest of this module, rational and

int * int are the same type. The outside world, however, doesn't have

to know that. So before I show you these functions, how

about I just show you, remind you, what the three signatures look like?

Now signature RATIONAL_A requires the module to implement rational as this data

type. Since our module does not, rational 3

does not have this signature. If we try to give it this signature, the

type checker will reject it. But it does have this second signature,

RATIONAL_B. As we'll see, we already saw it defines a

type rational. It defines it to equal int * int. That's a fine way to implement a

type rational. But, given that definition, it's going to

have to have these three functions, where rational is int * int.

The outside world, as usual, will not know that rational is int * int,

and then for RATIONAL_C it can have this signature, provided it has everything it

had before, as well as a function of type int arrow rational.

And I'll show you that at the end. So, let's see how struct rational 3 is

implemented and the fact that it can have type, the signature RATIONAL_B.

So rationals are now just pairs of ints, in all cases.

So here's make_frac. If the denominator is 0, raise an

exception. If y less than 0, then produce (-x,-y).

Just like before, but now it's just a pair of ints, there's no frac constructor

involved. Otherwise return (x,y).

Notice we're not treating whole numbers any differently.

So if this denominator is 1, then we'll just, return (x,y).

Okay? To add 2 fractions, well, add needs to

take 2 rationals. And each rational is an int * int, so

this pattern will do well. And, we're not going to reduce things.

That's an orthogonal choice, but like Rational2, we're just going to wait until

toString to reduce things. So we'll just return a*d + c*b in the

first position, and b*d in the second position.

And then finally, toString is a little more complicated, because we still want

to return everything in reduced form and treat whole numbers specially here.

So here's what you need to do, if x is 0, you have a numerator of 0, you better

return the string 0. Otherwise We have some more work to do

here with GCD and reduce. so here's a let fun gcd equal this in,

let's convert to a string the numerator concatenated with if the denominator is

one then don't do anything. Else, a slash, and then to string of the

denominator. So, I didn't empahasize this.

Up here, I've computed the num and the denominator, by dividing both by the gcd,

of the absolute value of x and y. So, a bunch of arithmetic, but the point

is, to not print the denominator, if the, if the numerator is 0, or if the

denominator is 1. And so overall, if we go back up here to

the signature RATIONAL_B, we've provided everything at the right type,

make_frac returned an int * int, add took two int * ints and returned an

int * int. And toString took and int * int and

returned a string. And then the outside world does not know

that rational is int star int. Now, if we want to implement Rational3,

we also need a function, of type int arrow rational.

This was this cute thing where, for our other structures, the data type binding

was providing this function, and then we are exposing just that part of the data

type binding to the outside world. Now, in our new structure we don't have a

data type binding, we don't have such a function being defined by it,

but we can still implement this signature provided we have this function.

And so, again, it's a little cute, but down here at the bottom, I just defined a

function whole, you're allowed to start functions with a capital letter if you

want to, that just takes in an i and returns this

rational, this int * int of (i,1), and it works great.

So, that is our third implementation. I want to emphasize, that when we do

signature matching of this structure, against either RATIONAL_B or RATIONAL_C,

a couple of interesting things are going on.

So let me emphasize those for you. the first is make_frac internally has

type int * int arrow int * int. And so that does match a signature where

we're exploding, exporting it as int * int arrow rational, because rational is

int * int. And the client will never be able to tell

that we're actually returning something of the same type we're taking in, because

the client doesn't know those are the same types.

Now what's interesting, is we could If we went back here to RATIONAL_B, we could

say that make_frac, instead of being int*int arrow rational, it's actually

rational arrow rational. Now this is a very bad signature. The

structure has this signature. The type checker will accept it.

But, the structure will be useless. Because if all the outside world knows is

that it can use these bindings, it's never going to be able to call make_frac,

add or toString, because it can never get its first rational to get started.

There's no way it can call any of these functions.

So there are programs out there that type check and are not useful.

Okay? So we really want to expose here that make_frac does take 2 ints as

arguments. So that's the first interesting thing.

The second interesting thing is this function, whole.

So let me go back and show that to you here at the bottom.

Based on what we know from type inference, this is going to have type

alpha arrow alpha * int. And that is indeed what the type checker

will give this function internally, when it goes to type check it.

But in our signature in RATIONAL_C, we said this had to have type int arrow

rational. And so somehow, the type checker, which

is fairly sophisticated and fairly smart, has to get from this first type to this

second type legally, and it turns out it can, because the rules of signature

matching let you take a polymorphic function and instantiate it to a

non-polymorphic function. So the first thing the type checker has

to figure out to do, for signature matching, is to realize that alpha can be

int, and therefore, this could also have typed the less flexible type, int arrow

int * int. We have to replace all the alphas

consistently, and now we see, that indeed, since rational inside the module

is int * int, we can, as we usually do, match this type against this type in the

signature, and its pretty neat that ML does that for us.

Notice that this does not have something like the type, alpha arrow int * int, or

you know, int arrow alpha * int. When you take a generic type, a

polymorphic type and make it less general, you have to replace all the

alphas with another type. The, this function hold does not have

these bottom two types, that doesn't make any sense.

It is not the case, that if you called whole with a string, you would get back

an int * int, so these types are just wrong and I will delete them.

It is interesting to me that inside this module, we actually could call whole with

a string for i, but outside the module, because of what the signature says, we

can only call it with an int, and therefore it will return irrational.

Okay? So, that is the more sophisticated and interesting details of this third

structure. But the high level point is that under

RATIONAL_B or RATIONAL_C, this structure is equivalent to our other two

structures, even though it implements the type

rational and in, and in an entirely different way.