.mli files and includes
Contents:
# .mli files
So we looked at implemeting signatures using the module type keyword, in the same file itself. But there is another way of doing this (which I hinted at before) which is to define a .mli file. This is an interface file which holds all your definitions so that you can decide what to keep public and what to keep private.
To give an example, let’s bring out the stack code we had and define its type interface separately:
let empty = [
elem :: s
failwith "Empty"
b
failwith "Empty"
a
So the above is the normal .ml file which contains the implementation. Then we can create its interface file like this:
(*Note that this is just denoting the type of a stack whose elements are alpha*)
And that’s what the world will see! (You should add more comments therefore!). This factoring of code into two files makes them into a compilation unit.
# Compilation units
A compilation unit is just the .ml + .mli files together. The reason the are a unit is because there are inherent rules to writing one:
- The names must match. That is the strings.ml file can have ONLY strings.mli as its interface file.
- This is effectively the SAME as what we had done last time.
That is, when we said module type NAME = sig ... end and then defined a module like module MNAME : NAME = struct ... end, then the NAME ensured which functions were public which weren’t and on.
This way of writing the compilation unit is effectively the same thing, with the added benefit that we can now work on the exposure separately from the core.
Note that the signature in this case is pretty much anonymous. You can see the above code as well, we haven’t specifically written this FOR the module we’ve defined.
# Includes
We’ve seen in the past how OCaml uses different operators for interger and float arithmetic (“+” vs “+.”). In abstract algebra there are rings and fields which let you abstract away. A ring for example in abstract algebra is an abstraction over additional and multiplication, but not just for numbers, for any other mathematical object (like polynomials for example).
So, let’s define the signature of rings:
=
Now we can use these signatures to create a module for say integer arithemtic:
=
Since we have bound the module definition to the interface Rings and Rings doesn’t specify the type of t, as far as OCaml is concerned it binds all definitions to t itself. It doesn’t care what exactly you do with the module types then!
We can play around, do something like:
let a =
(*a is abstract, no clue about what the answer is until you string it*)
string a;;
(*Now I could apply IntRings addition to a*)
let b = ;;
What if we want to create the successor function with IntRings?
(*successor for IntRings, takes `i`, and adds one to it. Assumes `i` is of type t*)
;;
(*or a little more refined*)
;;
(*or*)
;;
The type of this function is going to be: t -> t = <fun> or IntRings.t -> IntRings.t = <fun>. Now you can see how we would abstract away the dot in floating point operations?
=
And we won’t have to change our programs one bit, since succ3 for example can be defined using the same operations:
;;
And this will work for floats! Interesting right. So, what if we wanted to say have division as well? Rings in general don’t do divisons so we’ll have to go for fields.
=
Notice the massive code duplication here! We as programmers hate that (You better hate it too!). That’s where the idea behind includes comes into picture!
You can just include from any signature or module into any other signature or module and avoid code duplications
So the above Field, reduces to:
=
(*and we can define say IntFloat for div as well*)
=
So much cleaner isn’t it!
# Include vs Open
These both can seem very similar, but they have some differences (both open modules for instance):
=
=
=
The difference will be obvious if you LOOK closely at UTOP:
This means that the module N ended up “redefining” or “re-providing” the definitions and values in M. This is obviosuly as the name suggests!
So, you should use include, if you want to have your new module re-provide all those instances, otherwise use open if you just want to “use” the definitions in a module.