Parametrically polymorphic modules - ocaml

Here is a simple OCaml module type for a monad:
module type Monad = sig
type 'a t
val return : 'a -> 'a t
val bind : 'a t -> ('a -> 'b t) -> 'b t
end
I can instantiate this with any particular monad, such as the reader monad for some type r:
module Reader_monad : Monad = struct
type 'a t = r -> 'a
let return a = fun _ -> a
let bind o f = fun x -> f (o x) x
end
And I can parametrize it over the type r by using a functor:
module type Readable = sig type r end
module Reader (R : Readable) : Monad = struct
type 'a t = R.r -> 'a
let return a = fun _ -> a
let bind o f = fun x -> f (o x) x
end
However, the latter approach requires that I instantiate different instances of the functor for different types r. Is there any way to define a "parametrically polymorphic" module of type Monad that would give parametrically polymorphic functions like return : 'a -> ('r -> 'a)?
I think can get more or less what I want with a separate module type for "families of monads":
module type Monad_family = sig
type ('c, 'a) t
val return : 'a -> ('c, 'a) t
val bind : ('c, 'a) t -> ('a -> ('c, 'b) t) -> ('c, 'b) t
end
module Reader_family : Monad_family = struct
type ('c, 'a) t = 'c -> 'a
let return a = fun _ -> a
let bind o f = fun x -> f (o x) x
end
But if I have a substantial library of general facts about monads, this would require modifying it everywhere manually to use families. And then some monads are parametrized by a pair of types (although I suppose that could be encoded by a product type), etc. So I would rather avoid having to do it this way.
If this isn't directly possible, is there at least a way to instantiate the module Reader locally inside a parametrically polymorphic function? I thought I might be able to do this with first-class modules, but my naive attempt
let module M = Reader(val (module struct type r = int end) : Readable) in M.return "hello";;
produces the error message
Error: This expression has type string M.t
but an expression was expected of type 'a
The type constructor M.t would escape its scope
which I don't understand. Isn't the type M.t equal to int -> string?

I think this is the same issue as The type constructor "..." would escape its scope when using first class modules, where the module M doesn't live long enough. If you instead wrote
# module M = Reader(struct type r = int end);;
# M.return "hello";;
- : string M.t = <fun>
then this would work fine.
Separately, the Reader functor loses some type equalities that you might want. You can restore them by defining it as such:
module Reader (R : Readable) : Monad with type 'a t = R.r -> 'a = struct
type 'a t = R.r -> 'a
let return a = fun _ -> a
let bind o f = fun x -> f (o x) x
end

Related

How to lift a value into a monad

I'm trying to use the Writer monad in OCaml.
module Writer : Monad = struct
type 'a t = 'a * string
let return x = (x, "")
let (>>=) m f =
let (x, s1) = m in
let (y, s2) = f x in
(y, s1 ^ s2)
end
The statement below works.
Writer.(>>=) (Writer.return 2) (fun x -> Writer.return 1);;
But the statement below does not.
Writer.(>>=) (Writer.return 2) (fun x -> (x, "inc"));;
Error: This expression has type 'a * 'b but an expression was expected of type
'c Writer.t
I tried
Writer.(>>=) (Writer.return 2) (fun x -> ((x, "inc") : int Writer.t))
Error: This expression has type 'a * 'b but an expression was expected of type
int Writer.t
What am I doing wrong here? How can I lift a value into a monad in OCaml?
As mentioned, the code works with:
Writer.(>>=) (Writer.return 2) (fun x -> Writer.return 1);;
This is because the signature that describes the monad, here Monad, probably has this form:
module type Monad = sig
type 'a t
val return : 'a -> 'a t
val ( >>= ) : 'a t -> ('a -> 'b t) -> 'b t
end
As the concrete implementation of 'a t is not yet known (the type module potentially serving several implementations), 'a t is "abstract".
One approach would therefore be to "de-abstract" it.
This is possible by reflecting in the signature of the Writer module that we want to implement the Monad module while not making the type 'a t abstract, in this way:
module Writer : Monad with type 'a t = 'a * string = struct
type 'a t = 'a * string
let return x = (x, "")
let (>>=) m f =
let (x, s1) = m in
let (y, s2) = f x in
(y, s1 ^ s2)
end
In this way, the type `'a t' will no longer be abstract.
As a side note, I personally think it's not particularly bad that the Writer (or Reader, or State) type is abstract. The fact that it's a couple seems to me to be an implementation detail, some piping that doesn't need to be leaked.
A more fundamental issue is that the signature constraint
module Any_monad : Monad
is almost always a mistake in OCaml.
Signature constraints remove information that stick out of the signature. Thus, after the constraint, Any_monad could be replaced by any other monads fulfilling the monad signature (if we temporarily exclude side-effectful monads). For instance, you could replace the Writer monad with this very useful monad:
module Nil = struct
type 'a t = unit
let bind x f = ()
let (>>=) = bind
let return x = ()
end
In other words, an useful monad needs to have functions that are not part of the monad interface. In the case of the Writer monad that would be a write function:
module Writer: sig
include Monad
val write: string -> unit t
val read: 'a t -> string
end = struct
...
end
Notice that we have extended the signature before using it as a constraint.
Once, those functions available, your original code can be rewritten as:
Writer.(return 2 >>= fun x -> write "inc" >>= fun () -> return x)
You're constraining your monad implementation to be a Monad but, in fact, you want its type to have the Writer signature. To put it in other words, you're upcasting your implementation and forgetting that it also have to implement operations specific to the Writer monad. Obviously, this renders your Writer monad useless as you can't write anything.
To get it fixed, we first need to define the Writer module type, a common definition might look something like this,
module type Writer = sig
type state
val write : state -> unit t
val read : 'a t -> state t
val listen : 'a t -> ('a * state) t
val exec : unit t -> state
include Monad with type 'a t := 'a t
end
Now you need to implement the newly added operations with the state type set to string and then you will be able to use your Writer monad without breaking its abstraction,
module Writer : Writer with type state = string = struct
type state = string
type 'a t = 'a * string
let return x = (x, "")
let (>>=) m f =
let (x, s1) = m in
let (y, s2) = f x in
(y, s1 ^ s2)
let write x = (),x
let read (_,x) = x
let listen (x,s) = ((x,s),s)
let exec (_,x) = x
end
There are few libraries that implement monads in OCaml, so you don't need to re-invent it by your own. You can try the monads library that we develop at CMU. It could be installed with
opam install monads

Dynamic binding using a ref cell

I understand that you can't do this, but want to understand precisely why.
module M : sig
type 'a t
val call : 'a t -> 'a option
end = struct
type 'a t
let state : ('a t -> 'a option) ref = ref (fun _ -> None)
let call : ('a t -> 'a option) = fun x -> !state x
end
Results in:
Error: Signature mismatch:
Modules do not match:
sig
type 'a t
val state : ('_a t -> '_a option) ref
val call : '_a t -> '_a option
end
is not included in
sig
type 'a t
val call : 'a t -> 'a option
end
Values do not match:
val call : '_a t -> '_a option
is not included in
val call : 'a t -> 'a option
Why are the abstract types not compatible here?
My gut tells me it has everything to do with early vs late binding, but I'm looking for an exact description of what the type system is doing here.
One way to look at it is that your field state can't have the polymorphic value you ascribe to it, because mutable values can't be polymorphic. References are at most monomorphic (as indicated by the '_a notation for the type variable).
If you just try to declare a similar reference in the toplevel, you'll see the same effect:
# let lfr: ('a list -> 'a option) ref = ref (fun x -> None);;
val lfr : ('_a list -> '_a option) ref = {contents = <fun>}
The type variable '_a indicates some single type that hasn't yet been determined.
The reason that references can't be polymorphic is that it's unsound. If you allow references to be generalized (polymorphic) it's easy to produce programs that go horribly wrong. (In practice this usually means a crash and core dump.)
The issue of soundness is discussed near the beginning of this paper: Jacques Garrigue, Relaxing the Value Restriction (which I refer to periodically when I forget how things work).
Update
What I think you want is "rank 2 polymorphism". I.e., you want a field whose type is polymorphic. You can actually get this in OCaml as long as you declare the type. The usual method is to use a record type:
# type lfrec = { mutable f: 'a. 'a list -> 'a option };;
type lfrec = { mutable f : 'a. 'a list -> 'a option; }
# let x = { f = fun x -> None };;
val x : lfrec = {f = <fun>}
# x.f ;;
- : 'a list -> 'a option = <fun>
The following code compiles for me using lfrec instead of a reference:
module M : sig
type 'a t
val call : 'a t -> 'a option
end = struct
type 'a t
type lfrec = { mutable f: 'a. 'a t -> 'a option }
let state: lfrec = { f = fun _ -> None }
let call : ('a t -> 'a option) = fun x -> state.f x
end

Why can't I add type constraints when implementing a module type?

I was trying (just out of interest) to do this:
module type CAT = sig
type ('a, 'b) t
val id : ('a, 'a) t
val (#) : ('b, 'c) t -> ('a, 'b) t -> ('a, 'c) t
end
module Lst = struct
type ('a, 'b) t = 'a list constraint 'a = 'b
let id = []
let (#) = (#)
end
module L : CAT = Lst (* (error) *)
But I get:
Type declarations do not match:
type ('b, 'a) t = 'b list constraint 'a = 'b
is not included in
type ('a, 'b) t
Why isn't this safe? Everything that can see the concrete type can also see the constraint, so I don't think you could make something with a wrong type (e.g. call # with a (string, int) t argument).
Update: to those saying that my module doesn't implement the signature because it requires the types to be the same, consider that the following (which just wraps the lists in a List variant) is accepted despite having the same behaviour:
module Lst = struct
type ('a, 'b) t =
List : 'a list -> ('a, 'a) t
let id = List []
let (#) (type a) (type b) (type c) (a:(b, c) t) (b:(a, b) t) : (a, c) t =
match a, b with
| List a, List b -> List (a # b)
end
The example can be reduced to the type definition alone:
module type S =
sig
type ('a, 'b) t
end
module M =
struct
type ('a, 'b) t = 'a list constraint 'a = 'b
end
As Jeffrey already pointed out, M is not of type S, because it allows fewer applications of t: according to signature S, the type (int, string) t would be perfectly legal (it is well-formed), but M does not allow this type ((int, string) M.t is not a legal type, because it violates the explicit constraint).
All that is completely independent from the question whether the type is actually inhabited, i.e., whether you can construct values of the type. In your second example, the module makes the respective type well-formed, though it is uninhabited. Uninhabited types are legal, however, and sometimes even useful (see e.g. the concept of phantom types).
The type signature CAT is more general than the type of the Lst module. You need to put the type constraint on the abstract type too, i.e. type ('a, 'b) t constraint 'a = 'b.
This gives us the following:
module type CAT = sig
type ('a, 'b) t constraint 'a = 'b
val id : ('a, 'a) t
val (#) : ('b, 'c) t -> ('a, 'b) t -> ('a, 'c) t
end
which is printed as follows by the toplevel, showing a single type variable in the signature of (#):
module type CAT =
sig
type ('b, 'a) t constraint 'a = 'b
val id : ('a, 'a) t
val ( # ) : ('c, 'c) t -> ('c, 'c) t -> ('c, 'c) t
end
Error messages of the form "type x is not included in type y" refer to types or module types as specifications of sets of possible values, hence the use of the term "included".
In the case of a module implementation (Lst), we have a module type for it. Applying a signature (module type CAT) to a module is only allowed if that signature is as specialized (equal set) or more specialized (strict subset) than the original signature of the module.
One can write module X : sig val f : unit -> unit end = struct let f x = x end
but not module X : sig val f : 'a -> 'a end = struct let f () = () end. The latter gives the following error:
Error: Signature mismatch:
Modules do not match:
sig val f : unit -> unit end
is not included in
sig val f : 'a -> 'a end
Values do not match:
val f : unit -> unit
is not included in
val f : 'a -> 'a
This is different than placing type constraints on certain expressions, in which case the constraint is a mask to be applied (a set to intersect with) rather than a subset. For example it is fine to write let f : unit -> 'a = fun x -> x even though f's signature ends up being unit -> unit, a strict subset - or subtype - of unit -> 'a.
Your Lst module doesn't seem to me to have the type CAT. CAT allows the two types 'a and 'b to be independent. The Lst module requires them to be the same. If the L module were of type CAT then it should allow me to make something of type (string, int) t but it doesn't.
The error message is a little confusing, at least to me.

A strange example of typing in Ocaml

it is quite strange that this ocaml snippet is well typed by the toplevel. Look at the structure, if g is of type int->int as shown in the toplevel, the h x = g x part of the structure would not be able to get type-unified. So can any one clarify a bit?
module Mymodule : sig
val h:int ->int
val g: string-> string
end = struct
let g x = x
let h x = g x
end
This is the topelevel's response:
module Mymodule : sig val h : int -> int val g : string -> string end
The important thing to understand here is that OCaml performs type inference in a compositional manner, i.e., it will first infer type of struct ... end and only then it will match the inferred types against sig ... end to verify that the structure really does implement the signature.
For example, if you write
module Monkey : sig val f : int -> int end =
struct
let f x = x
end
then OCaml will be happy, as it will see that f has a polymorphic type 'a -> 'a which can be specialized to the required type int -> int. Because the sig ... end makes Monkey opaque, i.e., the signature hides the implementation, it will tell you that f has type int -> int, even though the actual implementation has a polymorphic type.
In your particular case OCaml first infers that g has type 'a -> 'a, and then that the type of h is 'a -> 'a as well. So it concludes that the structure has the type
sig val g : 'a -> 'a val h : 'a -> 'a end
Next, the signature is matched against the given one. Because a function of type 'a -> 'a can be specialized to int -> int as well as string -> string OCaml concludes that all is well. Of course, the whole point of using sig ... end is to make the structure opaque (the implementation is hidden), which is why the toplevel does not expose the polymorphic type of g and h.
Here is another example which shows how OCaml works:
module Cow =
struct
let f x = x
let g x = f [x]
let a = f "hi"
end
module Bull : sig
val f : int -> int
val g : 'b * 'c -> ('b * 'c) list
val a : string
end = Cow
The response is
module Cow :
sig
val f : 'a -> 'a
val g : 'a -> 'a list
val a : string
end
module Bull :
sig
val f : int -> int
val g : 'a * 'b -> ('a * 'b) list
val a : string end
end
I'd say that the string -> string typing isn't applied to g until it's exported
from the module. Inside the module (since you don't give it a type) it has the type
'a -> 'a. (Disclaimer: I'm not a module expert, trying to learn though.)

Functor compilation issue: Signature mismatch: Modules do not match

First the code:
module Boolean = struct
exception SizeMismatch
type boolean = T | F | Vec of boolean array
let to_bool v = match v with
T -> true
| F -> false
| _ -> raise SizeMismatch
end
module Logic = struct
type 'a var_t = { name: string; mutable value: 'a }
type 'a bexp = Const of 'a
| Var of 'a var_t
let eval exp = match exp with
Const x -> x
| Var x -> x.value
let make_var s v = { name = s; value = v }
let set v n = v.value <- n
let get_var_name v = v.name
let get_var_val v = v.value
end
module type EXP =
sig
type 'a var_t
type 'a bexp
val eval_exp : 'a bexp -> bool
val get_var_name : 'a var_t -> string
val get_var_val : 'a var_t -> 'a
end
module LogicExp =
struct
include Logic
let eval_exp exp = Boolean.to_bool (Logic.eval exp)
end
module FSM ( Exp : EXP ) =
struct
let print_var v = Printf.printf "%s = %d\n" (Exp.get_var_name v)
(Exp.get_var_val v)
end
module MyFSM = FSM(LogicExp)
let myvar = Logic.make_var "foo" 1;;
MyFSM.print_var myvar ;;
When I compile it I get the following error:
File "test.ml", line 57, characters 19-27:
Error: Signature mismatch:
Modules do not match:
sig
type 'a var_t =
'a Logic.var_t = {
name : string;
mutable value : 'a;
}
type 'a bexp = 'a Logic.bexp = Const of 'a | Var of 'a var_t
val eval : 'a bexp -> 'a
val make_var : string -> 'a -> 'a var_t
val set : 'a var_t -> 'a -> unit
val get_var_name : 'a var_t -> string
val get_var_val : 'a var_t -> 'a
val eval_exp : Boolean.boolean Logic.bexp -> bool
end
is not included in
EXP
Values do not match:
val eval_exp : Boolean.boolean Logic.bexp -> bool
is not included in
val eval_exp : 'a bexp -> bool
What I don't understand is how the more specific type isn't included in the more general type?
The error message is actually quite accurate:
Values do not match:
val eval_exp : Boolean.boolean Logic.bexp -> bool
is not included in
val eval_exp : 'a bexp -> bool
The MyFSM functor expects a module argument that, amongst other things, should contain a function eval_exp of type 'a bexp -> bool. That means that given a value of type 'a bexp for whatever choice of 'a the function should produce a value of type bool. You are, however, supplying a module that contains a function that only does this for one particular choice of 'a, i.e., the one where 'a is the type boolean from the module Boolean.
This quickest fix is to define your signature EXP as
module type EXP =
sig
type b (* added *)
type 'a var_t
type 'a bexp
val eval_exp : b bexp -> bool (* changed *)
val get_var_name : 'a var_t -> string
val get_var_val : 'a var_t -> 'a
end
so that eval_exp now operates on Boolean expressions over a fixed type b and then define LogicExp as
module LogicExp =
struct
type b = Boolean.boolean (* added *)
include Logic
let eval_exp exp = Boolean.to_bool (Logic.eval exp)
end
so that it fixes b to Boolean.boolean.
Implementing these changes will make your code compile.
Now, let us look at your question on "how the more specific type isn't included in the more general type?". This assumes that 'a bexp -> bool is indeed more general than boolean bexp -> bool, but in fact it isn't. A function type A -> B is considered more general than a function type C -> D if C is more general than A and B is more general than D:
A <: C D <: B
--------------------
C -> D <: A -> B
Note the "flipping" of C and A in the premise. We say that the function-space constructor ... -> ... is contravariant in its argument position (versus covariant in its result position).
Intuitively, a type is more general than another if it contains more values. To see why the function-space constructor is contravariant in its argument position, consider a function f of type A -> C for some types A and C. Now, consider a type B that is strictly more general than A, that is, all values in A are also in B, but B contains some values that are not in A. Thus, there is at least one value b to which we can assign type B, but not type A. Its type tells us that f knows how to operate on values of type A. However, if we were to (incorrectly!) conclude from A <: B that A -> C <: B -> C, then we could use f as if it had type B -> C and, hence, then we could pass the value b as an argument to f. But b is not of type A and f only knows how to operate on values of type A!
Clearly, covariance of ... -> ... in argument positions wouldn't work. To see that contravariance does work, consider the same types A, B, and C and now also consider a function g of type B -> C. That is, g knows how to operate on all values of type B. Contravariance of the function-space constructor in its argument position allows us to conclude that g can also be safely assigned the type A -> C. As we know that all values in A are also in B and g knows how to deal with all of B this poses no problems and we can safely pass values in A to g.