Here is a module type that represents values that support the usual operations from arithmetic, or more precisely, a field:
module type Arith = sig type t val zero : t val one : t val (+) : t -> t -> t val ( * ) : t -> t -> t val (~-) : t -> t end
There are a couple syntactic curiosities here. We have to write
( * ) instead
(*) because the latter would be parsed as beginning a comment. And
we write the
(~-) to indicate a unary negation operator.
Here is a module that implements that module type:
module Ints : Arith = struct type t = int let zero = 0 let one = 1 let (+) = Stdlib.(+) let ( * ) = Stdlib.( * ) let (~-) = Stdlib.(~-) end
Outside of the module
Ints, the expression
Ints.(one + one) is perfectly fine,
Ints.(1 + 1) is not, because
t is abstract: outside the module no one
is permitted to know that
t = int. In fact, the toplevel can't even give us
good output about what the sum of one and one is!
# Ints.(one + one);; - : Ints.t = <abstr>
The reason why is that the type
Ints.t is abstract: the module type doesn't
tell use that
int. This is actually a good thing in many cases:
code outside of
Ints can't rely on the internal implementation details of
Ints, and so we are free to change it.
Arith interface only has functions that return
t, so once you
have a value of type
t, all you can do is create other values of type
When designing an interface with an abstract type, you will almost certainly
want at least one function that returns something other than that type.
For example, it's often useful to provide a
to_string function. We could
add that to the
Arith module type:
module type Arith = sig (* everything else as before, and... *) val to_string : t -> string end
And now we would need to implement it as part of
module Ints : Arith = struct (* everything else as before, and... *) let to_string = string_of_int end
Now we can write:
# Ints.(to_string (one + one));; - : string = "2"