[MUSIC] In this segment, we're going to give types to modules.
We'll call those types signatures. We'll start with the basic idea, and
then, show the most important thing signatures are used for,
which is to hide things inside of modules from all the code outside the module.
So, I really do want you to think of a signature as a type for a module,
but the same way we call modular structures, we will call their types
signatures, we're just using different english words.
And what a signature indicates about a module, is what bindings are defined in
there and what type do they have. So, we can define a signature, actually,
separately from any particular module, and then, we can say that module has the
signature. So let's see how this works with the
example we used in the previous segment. So we can define a signature, as you see
here at the top, we use the keyword signature.
It's somewhat conventional to use all capital letters.
You don't have to do that. Say this is the MATHLIB signature,
then, sig, which is a key word, although, it's not an English word. The types of
bindings just like we've been seeing the RPL print out,
so we would write val fact:int->int to say any module with the MATHLIB signature
must have a variable fact of type int->int, however it's defined.
It doesn't matter if it's defined via a function binding, or a variable binding
or any other way. The point is that it has such a thing
that it defines, similarly half_pi, and doubler, and then
the keyword end. Now, our structure that we defined in the
previous segment, my MATHLIB, could have this signature.
It defines all the things that the MATHLIB signature requires.
And in such a situation you can write :> and the name of the signature in your
structure definition between the structure name and the =.
And now, the structure will only type check if you provide everything at an
appropriate type. So if this signature had something that
the structure did not provide, we would get a nice error message from the type
checker reminding us that we didn't provide it, and similarly, if we provided
something, but not at the correct type. Okay?
so, in general, the way we do signatures, is you just write signature, name of your
signature, equal sig, the types for your bindings very much like the REPL has been
printing things out for us in N. And in fact, one way you can get a start
to your signature is paste in what the REPL says a structure has. in this
signature, you can have things for a variable, types data type and exceptions,
we'll see examples of that in future segment and then when you want to ascribe
a ignature. to a structure, you do it like this.
So the advantage of having this, as we will see, so we can define a signature
once, and then, have different structures in our program that all have that
signature and we can could reuse the signature name.
And as I mentioned the module simply won't type-check unless it has everything
at the right types. So if we see that over in our file here,
I have the same structure I had before, but now I have the signature as you've
seen from the, on the slides, then my structure, and then, later here,
in the file, the same uses, and if I load all this up in the rebel we now see that
it defined the signature. Having done so, all it's going to tell us about the
structure of my MATHLIB is that it has that signature.
And then we have pi and 28 and everything works great.
Alright? So that's the basics of signatures, but now let me show you what
they're actually good for and that is hi-, hiding things.
The real value of signatures is not to document and write down the type of
everything in the module. It's to hide implementation details,
which is the most important strategy for writing correct, robust, reusible
software, is to say to the outside world, I don't want you using eveything defined
in this module. I want control over what's public and
what's private, what you can use, and what you should not assume even exists.
So we actually have ways to do this already in ML, whether it's with local
helper functions or what not. you know, here are three versions of a
double function and there are all sorts of reasons why clients of this function
have no idea how it's implemented. It doesn't matter whether you write x*2
or x+x or x*y where y happens to be equal to two where the function is defined.
We also know that if we define helper functions locally, no one outside of the
let expression where the function is defined, even knows that function exist.
What module systems are giving us, in addition to other things, is the ability
to do this for a sequence of bindings at the top level of the module.
It would be really convenient to have some functions that are private and some
are public. In a lot of programming languages, we do
this by actually marking the the functions, private or public.
In ML, we do it a little differently. It's nice to see something different.
what we do in ML, is we just write our module how we want, and then in the
signature, we leave things out and anything not in the signature cannot be
used outside the module. So here's how that works.
If I just go back to my example, and I take out one of the bindings from the
signature, the module, the structure can still have that type.
The module is allowed to have things not in the signature, it just has to have
everything that is in the signature. And whatever is not in the signature
cannot be used outside the module, it can still be used inside the module.
So let me show that over here with the code.
So let me go back up here, and let me comment out from my signature, the
doubler function, so no one on the outside can know that doubler exists.
but I can still use it inside, so I could have a value eight here that would be a
doubler of four, that would work just fine.
And, then, if I go over here and recompile everything, I'm actually going
to get an error message. And, we will see that the error message
is, on line 27 there is an unbound variable, MyMathLib.doubler,
and indeed there is. Right here on line 28, there is no such
thing as MyMathLib.doubler outside of the module and so I simply cannot use it.
Alright? So having fixed that, everything should now work.
I still have my val pi, I still have my structure, MyMathLib that has the
signature, MATHLIB. Okay. So that is hiding for us,
and that is one of the ways we are going to use modules to hide things from the
rest of the program. We'll see that signatures have even more
power that will let us code up neat things in the upcoming segment.