# 5.6. Module Type Constraints¶

We have extolled the virtues of encapsulation. Now we’re going to do something that might seem counter-intuitive: selectively violate encapsulation.

As a motivating example, here is a module type that represents values that support the usual operations from arithmetic, or more precisely, a field:

```module type Field = sig
type t
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val to_string : t -> string
end
```
```module type Field =
sig
type t
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val to_string : t -> string
end
```

Recall that we must write `( * )` instead of `(*)` because the latter would be parsed as beginning a comment. And we write the `~` in `(~-)` to indicate a unary negation operator.

This is a bit weird of an example. We don’t normally think of numbers as a data structure. But what is a data structure except for a set of values and operations on them? The `Field` module type makes it clear that’s what we have.

Here is a module that implements that module type:

```module IntField : Field = struct
type t = int
let zero = 0
let one = 1
let ( + ) = Stdlib.( + )
let ( * ) = Stdlib.( * )
let ( ~- ) = Stdlib.( ~- )
let to_string = string_of_int
end
```
```module IntField : Field
```

Because `t` is abstract, the toplevel can’t give us good output about what the sum of one and one is:

```IntField.(one + one)
```
```- : IntField.t = <abstr>
```

But we could convert it to a string:

```IntField.(one + one |> to_string)
```
```- : string = "2"
```

We could even install a pretty printer to avoid having to manually call `to_string`:

```let pp_intfield fmt i =
Format.fprintf fmt "%s" (IntField.to_string i);;

#install_printer pp_intfield;;

IntField.(one + one)
```
```val pp_intfield : Format.formatter -> IntField.t -> unit = <fun>
```
```- : IntField.t = 2
```

We could implement other kinds of fields, too:

```module FloatField : Field = struct
type t = float
let zero = 0.
let one = 1.
let ( + ) = Stdlib.( +. )
let ( * ) = Stdlib.( *. )
let ( ~- ) = Stdlib.( ~-. )
let to_string = string_of_float
end
```
```module FloatField : Field
```

Then we’d have to install a printer for it, too:

```let pp_floatfield fmt f =
Format.fprintf fmt "%s" (FloatField.to_string f);;

#install_printer pp_floatfield;;

FloatField.(one + one)
```
```val pp_floatfield : Format.formatter -> FloatField.t -> unit = <fun>
```
```- : FloatField.t = 2.
```

Was there really a need to make type `t` abstract in the field examples above? Arguably not. And if it were not abstract, we wouldn’t have to go to the trouble of converting abstract values into strings, or installing printers. Let’s pursue that idea, next.

## 5.6.1. Specializing Module Types¶

In the past, we’ve seen that we can leave off the module type annotation, then do a separate check to make sure the structure satisfies the signature:

```module IntField = struct
type t = int
let zero = 0
let one = 1
let ( + ) = Stdlib.( + )
let ( * ) = Stdlib.( * )
let ( ~- ) = Stdlib.( ~- )
let to_string = string_of_int
end

module _ : Field = IntField
```
```module IntField :
sig
type t = int
val zero : int
val one : int
val ( + ) : int -> int -> int
val ( * ) : int -> int -> int
val ( ~- ) : int -> int
val to_string : int -> string
end
```
```IntField.(one + one)
```
```- : int = 2
```

There’s a more sophisticated way of accomplishing the same goal. We can specialize the `Field` module type to specify that `t` must be `int` or `float`. We do that by adding a constraint using the `with` keyword:

```module type INT_FIELD = Field with type t = int
```
```module type INT_FIELD =
sig
type t = int
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val to_string : t -> string
end
```

Note how the `INT_FIELD` module type now specifies that `t` and `int` are the same type. It exposes or shares that fact with the world, so we could call these “sharing constraints.”

Now `IntField` can be given that module type:

```module IntField : INT_FIELD = struct
type t = int
let zero = 0
let one = 1
let ( + ) = Stdlib.( + )
let ( * ) = Stdlib.( * )
let ( ~- ) = Stdlib.( ~- )
let to_string = string_of_int
end
```
```module IntField : INT_FIELD
```

And since the equality of `t` and `int` is exposed, the toplevel can print values of type `t` without any help needed from a pretty printer:

```IntField.(one + one)
```
```- : IntField.t = 2
```

Programmers can even mix and match built-in `int` values with those provided by `IntField`:

```IntField.(1 + one)
```
```- : IntField.t = 2
```

The same can be done for floats:

```module type FLOAT_FIELD = Field with type t = float

module FloatField : FLOAT_FIELD = struct
type t = float
let zero = 0.
let one = 1.
let ( + ) = Stdlib.( +. )
let ( * ) = Stdlib.( *. )
let ( ~- ) = Stdlib.( ~-. )
let to_string = string_of_float
end
```
```module type FLOAT_FIELD =
sig
type t = float
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val to_string : t -> string
end
```
```module FloatField : FLOAT_FIELD
```

It turns out there’s no need to separately define `INT_FIELD` and `FLOAT_FIELD`. The `with` keyword can be used as part of the `module` definition, though the syntax becomes a little harder to read because of the proximity of the two `=` signs:

```module FloatField : Field with type t = float = struct
type t = float
let zero = 0.
let one = 1.
let ( + ) = Stdlib.( +. )
let ( * ) = Stdlib.( *. )
let ( ~- ) = Stdlib.( ~-. )
let to_string = string_of_float
end
```
```module FloatField :
sig
type t = float
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val to_string : t -> string
end
```

## 5.6.2. Constraints¶

Syntax.

There are two sorts of constraints. One is the sort we saw above, with `type` equations:

• `T with type x = t`, where `T` is a module type, `x` is a type name, and `t` is a type.

The other sort is a `module` equation, which is syntactic sugar for specifying the equality of all types in the two modules:

• `T with module M = N`, where `M` and `N` are module names.

Multiple constraints can be added with the `and` keyword:

• `T with constraint1 and constraint2 and ... constraintN`

Static semantics.

The constrained module type `T with type x = t` is the same as `T`, except that the declaration of `type x` inside `T` is replaced by `type x = t`. For example, compare the two signatures output below:

```module type T = sig type t end
module type U = T with type t = int
```
```module type T = sig type t end
```
```module type U = sig type t = int end
```

Likewise, `T with module M = N` is the same as `T`, except that the any declaration `type x` inside the module type of `M` is replaced by `type x = N.X`. (And the same recursively for any nested modules.) It takes more work to give and understand this example:

```module type XY = sig
type x
type y
end

module type T = sig
module A : XY
end

module B = struct
type x = int
type y = float
end

module type U = T with module A = B

module C : U = struct
module A = struct
type x = int
type y = float
let x = 42
end
end
```
```module type XY = sig type x type y end
```
```module type T = sig module A : XY end
```
```module B : sig type x = int type y = float end
```
```module type U = sig module A : sig type x = int type y = float end end
```
```module C : U
```

Focus on the output for module type `U`. Notice that the types of `x` and `y` in it have become `int` and `float` because of the `module A = B` constraint. Also notice how modules `B` and `C.A` are not the same module; the latter has an extra item `x` in it. So the syntax `module A = B` is potentially confusing. The constraint is not specifying that the two modules are the same. Rather, it specifies that all their types are constrained to be equal.

Dynamic semantics.

There are no dynamic semantics for constraints, because they are only for type checking.