Let's say we want to abstract out OCaml's arithmetic operators for ints and floats. Abstract algebra (the mathematical concept) allows us to do this using structures called Rings) and Fields). We can define structures for Ring and Field:
( ~- ) is unary negation (for example, to multiplying by -1).
module type Ring = sig
type t
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val string : t -> string
end
module IntRingRep = struct
type t = int
let zero = 0
let one = 1
let ( + ) = Stdlib.( + )
let ( - ) = Stdlib.( - )
let ( * ) = Stdlib.( * )
let ( ~- ) = Stdlib.( ~- )
let string = string_of_int
end
module IntRing : Ring = IntRingRep
module type Ring =
sig
type t
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val string : t -> string
end
module IntRingRep :
sig
type t = int
val zero : int
val one : int
val ( + ) : int -> int -> int
val ( - ) : int -> int -> int
val ( * ) : int -> int -> int
val ( ~- ) : int -> int
val string : int -> string
end
module IntRing : Ring
Now let's see how this works:
IntRing.zero;; (* we can see it's 0 *)
IntRingRep.zero;; (* abstract *)
IntRingRep.(zero |> string);; (* string representation *)
- : IntRing.t = <abstr>
- : int = 0
- : string = "0"
Let's do some math!
IntRing.(string @@ zero + zero);; (* -> 0 *)
IntRing.(string @@ zero + one);; (* -> 1 *)
IntRing.(string @@ zero + one + one);; (* -> 2 *)
- : string = "0"
- : string = "1"
- : string = "2"
Now we can implement a ring for floating point numbers:
module FloatRingRep = struct
type t = float
let zero = 0.
let one = 1.
let ( + ) = Stdlib.( +. )
let ( - ) = Stdlib.( -. )
let ( * ) = Stdlib.( *. )
let ( ~- ) = Stdlib.( ~-. )
let string = string_of_float
end
module FloatRing : Ring = FloatRingRep
module FloatRingRep :
sig
type t = float
val zero : float
val one : float
val ( + ) : float -> float -> float
val ( - ) : float -> float -> float
val ( * ) : float -> float -> float
val ( ~- ) : float -> float
val string : float -> string
end
module FloatRing : Ring
FloatRing.(string @@ zero + zero);; (* -> 0 *)
FloatRing.(string @@ zero + one);; (* -> 1 *)
FloatRing.(string @@ zero + one + one);; (* -> 2 *)
- : string = "0."
- : string = "1."
- : string = "2."
See, we have 0. and so on, so we know it's a float!
What if we wanted to provide division? Well, division is actually part of a field, not a ring. What if we wanted to implement a field?
~We could copy code~ no wait don't! That's never a good idea. We can use an include to include all the contents of another signature or structure in the current one:
module type Field = sig
include Ring
val ( / ) : t -> t -> t
end
module IntFieldRep = struct
include IntRingRep
let ( / ) = Stdlib.( / )
end
module IntField : Field = IntFieldRep;;
module type Field =
sig
type t
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val string : t -> string
val ( / ) : t -> t -> t
end
module IntFieldRep :
sig
type t = int
val zero : int
val one : int
val ( + ) : int -> int -> int
val ( - ) : int -> int -> int
val ( * ) : int -> int -> int
val ( ~- ) : int -> int
val string : int -> string
val ( / ) : int -> int -> int
end
module IntField : Field
Now, if someone ever changes the implantation of rings, our changes would be duplicated. Let's do the same for our floating point numbers:
module FloatFieldRep = struct
include FloatRing
let ( / ) = Stdlib.( /. )
end
module FloatField : Field = FloatFieldRep;;
module FloatFieldRep :
sig
type t = FloatRing.t
val zero : t
val one : t
val ( + ) : t -> t -> t
val ( * ) : t -> t -> t
val ( ~- ) : t -> t
val string : t -> string
val ( / ) : float -> float -> float
end
File "[7]", line 6, characters 28-41:
6 | module FloatField : Field = FloatFieldRep;;
^^^^^^^^^^^^^
Error: Signature mismatch:
...
Values do not match:
val ( / ) : float -> float -> float
is not included in
val ( / ) : t -> t -> t
File "[6]", line 3, characters 1-24: Expected declaration
File "[7]", line 3, characters 5-10: Actual declaration
Uh oh... what happened there? Well, we need to use FloatFieldRep instead of FloatField so that our types are correct:
module FloatFieldRep = struct
include FloatRingRep
let ( / ) = Stdlib.( /. )
end
module FloatField : Field = FloatFieldRep;;
module FloatFieldRep :
sig
type t = float
val zero : float
val one : float
val ( + ) : float -> float -> float
val ( - ) : float -> float -> float
val ( * ) : float -> float -> float
val ( ~- ) : float -> float
val string : float -> string
val ( / ) : float -> float -> float
end
module FloatField : Field