Related
In this question, it was explained to me how to correctly pattern-match GADT types to get alternative return types using universally quantified type variables and locally abstract types. Notwithstanding the misconceptions in my original example, the explanation did not express: (1) how the type matching is done given that the type system seem to require, when using universally quantified type variables, that alternative branches have the same return type and (2) show an elegant way to mix universally quantified variables and locally abstract type for desired GADT return type outcome.
The type from the original question cited above is:
type
liLabel_t = string (* Instruction name (label) *)
and
context_t = string
and
'a context_list_t = 'a list
and
'a liChooser_t = 'a -> int (* get index of i-th list entry *)
and
('a, 'b) liInstr_t =
LiExec: 'a -> ('a, 'b) liInstr_t (* executable operation *)
| LiExecTRY: ('a, _) liInstr_t (* Ignore: Experiment on GADT *)
| LiLab: liLabel_t -> ('a, 'b) liInstr_t (* instruction label *)
| LiLabTRY: (liLabel_t, _) liInstr_t (* Ignore: Experiment on GADT *)
| LiSeq: 'a liChooser_t * 'b list -> ('a, 'b) liInstr_t (* sequence *)
| LiAlt: 'a liChooser_t * 'b list -> ('a, 'b) liInstr_t (* choice *)
| LiLoop: 'a liChooser_t * 'b list -> ('a, 'b) liInstr_t (* loop *)
| LiName: 'a liChooser_t * liLabel_t * 'b context_list_t ->
('a, 'b) liInstr_t (* change context *)
| Err_LiInstr: ('a, 'b) liInstr_t (* error handling *)
| Nil_LiInstr: ('a, 'b) liInstr_t (* no action *)
In experimenting, when alternatives all have integer return types such as in:
let ft1: 'b 'c. ('b, 'c) liInstr_t -> 'b = function
(* *) | LiExec n -> 2
(* *) | LiExecTRY -> 24
(* *) | LiLab s -> 4
(* *) | LiLabTRY -> 44
(* *) | LiSeq (f, il) -> 42
(* *) | LiAlt (f, il) -> 51
(* *) | LiLoop (f, il) -> 52
(* *) | LiName (f, il, ic) -> 53
(* *) | Err_LiInstr -> 54
(* *) | Nil_LiInstr -> 55
* ;;
I receive the error:
Line 2, characters 21-22:
2 | (* *) | LiExec n -> 2
^
Error: This expression has type int but an expression was expected of type
liLabel_t = string
and when I comment out these branches that expect string types in their GADT parameters (like so):
(* * ) | LiLab s -> 4
(* *) | LiLabTRY -> 44
( * *)
I get the errors:
...
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
LiLab _
Lines 1-11, characters 45-26:
...
Error: This definition has type 'c. (int, 'c) liInstr_t -> int
which is less general than 'b 'c. ('b, 'c) liInstr_t -> 'b
Ignoring the non-exhaustive pattern-matching warning (which is due to variants being commented out), we have a less general type definition of the function. The explanation for this is found in the answers to the original question.
If, however, all the branches have the string type, we sometimes get a similar "less general" error as above but mostly the error:
Line 3, characters 22-26:
3 | (* *) | LiExecTRY -> "24"
^^^^
Error: This expression has type string but an expression was expected of type
b
I have not yet figured out why this behaviour, but a suspicion is that the branches:
(* *) | LiExecTRY -> "24"
(* *) | LiLab s -> "4"
if available, somehow compel one to have a string return type for all variants whereas arbitrary types could be returned if they were not used. In that wise, it seems to me that their LiLabel_t (i.e., string) type argument in the GADT is doing this coercion. But how?
If we use local abstract types with all return types being integers, as in:
# let ft1: type b c. (b, c) liInstr_t -> b = function
(* *) | LiExec n -> 2
(* *) | LiExecTRY -> 24
...
we get the error
Line 2, characters 21-22:
2 | (* *) | LiExec n -> 2
^
Error: This expression has type int but an expression was expected of type b
which is as expected as explained in answers to the original question on GADT and local abstract types: the abstract return type b was expected as it was never matched via equational constraints in the GADT to a more concrete type. A similar error arises when I change all the return types (except the first) to string.
My overall questions, effectively raised within the original GADT question are:
In my examples, why were all my return types compelled to be int, or to be string (i.e., LiLabel_t) except maybe for the first variant, before being rejected later for being less general than the return type b. What is the reasoning followed within the type checker?
Is there an elegant way to combine, in various permutations, universally quantified types and locally abstract types in GADT functions, as there apparently are no syntactically correct ways of doing so. For, if we try to mix universally quantified type variables and locally abstract type variable, we always get an error such as:
Line 1, characters 10-14:
1 | let ft1: (type d) 'b 'c. ('b, 'c) liInstr_t -> d = function
^^^^
Error: Syntax error
Question 1:
Your example can be reduced to
type ('a,'b) t =
| A of 'a
| B: (string,_) t
let error (x:('a,'b) t) : 'a= match x with
| A n -> 2
| B -> "hi"
The first branch implies that 'a = int. Then the second branch B has type (string,'c) t, which implies that
('a,'b) t=(string,'c) t and thus 'a = string, which is a contradiction. Indeed, only abstract types can have local equation in pattern matching and 'a and 'b are unification type variables.
(It is hard to guess what went wrong in the other cases, that you only ever hinted at, so I will ignore them.)
Question 2:
If you return string in all branches, then your function
let ok (x:('a,'b) t): 'a= match x with
| A n -> n
| B -> "hi"
is well typed by unifying 'a with string, giving the type (string,'b) t -> string to ok. But if you ask the typechecker to check that the type is polymorphic, it will notify you that the type of ok is not polymorphic:
let not_polymorphic: 'a 'b. ('a,'b) t -> 'a= function
| A n -> n
| B -> "hi"
You can notice here that the polymorphism check is done after typechecking the body of the function. Thus the error mentions the inferred type for the function body:
Error: This definition has type 'c. (string, 'c) t -> string
which is less general than 'a 'b. ('a, 'b) t -> 'a
Question 3.
Yes, it is possible, but this is uncommon occurrence
let f (type d) (x:d) =
let g : 'a 'b. ('a,'b) t -> d = fun _ -> x
in g
Most use cases are far better served by the shorthand syntax:
let f: type a b d. d -> (a,b) t -> d = fun d _ -> d
P.S: Would you mind asking one question at a time after trying to reduce your problematic case? It is hard to guess the root issues of the later questions when you layer questions upon questions.
I want to exploit GADT to implement the type ('a, 'b) liInstr_t in order to hold various types of instructions which are recursively decoded into basic operations (if need be) that then executed. (Eventually, I should construct more abstract types over them but in a mechanical, compositional and scripted fashion.) Unfortunately, I have difficulties associating the locally abstract types from pattern-matching function argument with the alternative concrete return types desired for the GADT.
I believe I'm missing something fundamental or making wrong assumptions though I have looked at the ocaml 4.10.0 manual on locally abstract types and gadts, the Real world Ocaml book, and the responses to similar questions such as here and here. This is because I seem to follow their explanations but cannot somehow apply them to my task.
From the above, I understand that polymorphic type variables annotate functions so that they can take on (be unified with) arbitrary types compatible with their constraints, and that locally abstract types let us have different types along alternative paths through a pattern-matching expression, say. Also, the local abstract types cannot be unified but can be refined to concrete types compatible with GADT result types. As such, GADTs can recurse over polymorphic types, and aggregate multiple result types into a single sum type.
I deliberately let the type ('a, 'b) liInstr_t have two type variables (so I can add more later), and its variants capture various constraint formats and scenarios I may have to use together.
type
liLabel_t = string (* Instruction name (label) *)
and
context_t = string (* TODO: execution context *)
and
'a context_list_t = 'a list
and
'a liChooser_t = 'a -> int (* get index of i-th list entry *)
and
('a, 'b) liInstr_t =
LiExec: 'a -> ('a, 'b) liInstr_t (* executable operation *)
| LiExecTRY: ('a, _) liInstr_t (* Ignore: Experiment on GADT *)
| LiLab: liLabel_t -> ('a, 'b) liInstr_t (* instruction label *)
| LiLabTRY: (liLabel_t, _) liInstr_t (* Ignore: Experiment on GADT *)
| LiSeq: 'a liChooser_t * 'b list -> ('a, 'b) liInstr_t (* sequence *)
| LiAlt: 'a liChooser_t * 'b list -> ('a, 'b) liInstr_t (* choice *)
| LiLoop: 'a liChooser_t * 'b list -> ('a, 'b) liInstr_t (* loop *)
| LiName: 'a liChooser_t * liLabel_t * 'b context_list_t ->
('a, 'b) liInstr_t (* change context *)
| Err_LiInstr: ('a, 'b) liInstr_t (* error handling *)
| Nil_LiInstr: ('a, 'b) liInstr_t (* no action *)
After experimenting, the sample function used is:
let ft1: type b c. (b, c) liInstr_t -> b = function
(* *) | LiExec n -> n
(* *) | LiExecTRY -> "4"
(* *) | LiLab s -> "LiLab"
(* *) | LiLabTRY -> "LiLabTRY"
(* *) | LiSeq (f, il) -> "LiSeq"
(* *) | LiAlt (f, il) -> "LiAlt"
(* *) | LiLoop (f, il) -> "LiLoop"
(* *) | LiName (f, il, ic) -> "LiName"
(* *) | Err_LiInstr -> "Err_LiInstr"
(* *) | Nil_LiInstr -> "Nil_LiInstr"
;;
and it gave the error:
Line 3, characters 22-25:
3 | (* *) | LiExecTRY -> "4"
^^^
Error: This expression has type string but an expression was expected of type
b
I still got errors when I changed the function annotation (and typing), or commented out some alternatives in the function pattern matching and the GADT type variants. Some of the errors (elided for brevity) were obtained as follows:
Using an extra locally-typed variable:
let ft1 : type b c d. (b, c) liInstr_t -> d = function ...
2 | (* *) | LiExec n -> n
^
Error: This expression has type b but an expression was expected of type d
Using only polymorphic type variables:
let ft1: 'b 'c. ('b, 'c) liInstr_t -> 'b = function ...
Error: This definition has type 'c. (liLabel_t, 'c) liInstr_t -> liLabel_t
which is less general than 'b 'c. ('b, 'c) liInstr_t -> 'b
My questions then are the following:
How can we capture and use the abstract types identified with alternative paths? A locally abstract type should bind (or be refined) to compatible concrete type(s) for values found in the resulting expression, or can be ignored, right? Ignoring recursion, this example:
let rec eval : type a. a term -> a = function
| Int n -> n (* a = int *)
| Add -> (fun x y -> x+y) (* a = int -> int -> int *)
| App(f,x) -> (eval f) (eval x)
(* eval called at types (b->a) and b for fresh b *)
on expression evaluation in the ocaml manual seems to suggest that is the case, at least for a 1-parameter GADT type. So, why aren't my types b and c not suitably bound (or refined) in the return type? And if they are binding (or being refined), which should bind to abstract type b and which to c, if at all? How can I find values for my return type so they can correctly associate with the abstract, value-less types reaching them. For, there is seems no way to obtain a result that has the type b in my first error above!
Why am I forced to have the same result type for the alternative paths to succeed (string type in my examples) whereas all possible result types of the GADT should be admissible. In this regard, the first variant (LiExec n -> n) seemed forced to have type string! Also, the abstract types and polymorphic variables along execution paths seem irrelevant to the result type!
I could not reproduce it but at one point, making the first variant LiExec n -> 4 seemed to require integer return values from all alternative pattern matches. If indeed this is the case, why should abstract types on alternative paths require values from the same non-GADT return type? (This behaviour is of non-polymorphic types, right?)
To work around incomprehensible issues on polymorphic types and locally abstract types, is there a simple way to mix them in a constraint? Various permutations to mix them always seem to result in a syntax error. e.g.:
let ft1: (type d) 'b 'c. ('b, 'c) liInstr_t -> d = function
^^^^
Error: Syntax error
Suppose we have the following GADT:
type _ simple_gadt =
| Con : 'a -> 'a simple_gadt
The type signature of Con can be understood as ('a : Type) -> 'a -> 'a simple_gadt (not real OCaml syntax); in other words, it takes a type as its first argument, and the rest of the type is dependent on this input type. The client provides the type; for example:
let value : int simple_gadt = Con 0
Implicitly, you can understand this definition as really meaning let value = Con(type int, 0), where the type is given as an argument (again, not real OCaml syntax).
When you write a function that takes a 'a simple_gadt as an argument, you don't know what 'a is. 'a is said to be an "existential type" provided by the caller of the function. Consider the following function:
let f (type a) (param : a simple_gadt) : a = match param with
| Con x -> x
The type of f is 'a . 'a simple_gadt -> 'a. A client can evaluate f (Con 0) and get back 0, of type int. A client can also evaluate f (Con true) and get back true, of type bool. The definition of the function has no control over what the actual type 'a is; only the caller does.
Suppose we attempt to define:
let g (type a) (param : a simple_gadt) : a = match param with
| Con _ -> ""
One would be able to evaluate g (Con 0) and get back "", a string, but based on the type of Con 0, the output of the function should be an int. This is clearly a type error, so g has an ill-typed definition, and the compiler rightfully rejects it. Likewise, your definition
let ft1: type b c. (b, c) liInstr_t -> b = function
(* ... *)
(* *) | LiExecTRY -> "4"
(* ... *)
is ill-typed because it assumes that b is string, while b could be any type that the caller provides. It looks like you have other similar type errors because you are attempting to pick more specific types for the existential types.
If the caller can choose any type, how can one use GADTs to "refine" the type variable to a more concrete type? The only way to do this is through the information that the caller provides.
Consider the following type definition:
type _ term =
| Abs : ('a -> 'b) -> ('a -> 'b) term
| App : ('a -> 'b) term * 'a term -> 'b term
| Bool : bool -> bool term
In a GADT, each constructor can make the type parameters more specific. Therefore, by pattern matching against each constructor, a function can refine the existential type parameter.
Consider this function on the GADT defined above:
let rec eval : 'a . 'a term -> 'a =
fun (type a) (term : a term) : a ->
match term with
| Abs f -> f
| App(f, x) -> (eval f) (eval x)
| Bool b -> b
In the Abs f case, Abs f is known to have type ('a -> 'b) term for some 'a and 'b by the definition of Abs. Similar reasoning applies for the App(f, x) and Bool b cases.
What's a universally quantified type from the caller's perspective (i.e. the caller can pick any type) must be an existentially quantified type from the callee's perspective (i.e. the callee must work with some fixed arbitrary type that the caller provides).
In brief, the type li_Instr_t as defined is not an interesting GADT and it can be rewritten to the strictly equivalent ADT
type ('a, 'b) liInstr_t =
| LiExec of 'a
| LiExecTRY
| LiLab of liLabel_t
| LiLabTRY
| LiSeq of 'a liChooser_t * 'b list
| LiAlt of 'a liChooser_t * 'b list
| LiLoop of 'a liChooser_t * 'b list
| LiName of 'a liChooser_t * liLabel_t * 'b context_list_t
| Err_LiInstr
| Nil_LiInstr
because the type declaration never introduces equations (or existential quantifications) between the result type and the constructor of the GADT.
If we look at a simple example for GADT:
type ('elt,'array) compact_array =
| String: (char, String.t) compact_array
| Float_array: (float, Float.Array.t) compact_array
| Standard: ('a, 'a array) compact_array
let init: type elt array.
(elt,array) compact_array -> int -> (int -> elt) -> array =
fun kind n f -> match kind with
| String -> String.init n f
| Float_array -> Float.Array.init n f
| Standard -> Array.init n f
The difference is that the constructor String constrains the type of compact_array to be (char,string) compact_array. Thus, when I observe String in the pattern matching above, I can introduce the equation
elt=char and array=string in the branch String and use those equation locally . Similarly, after observing the constructor Float_array in the pattern matching, I can work with the equation elt=float and array=Float.Array.t inside the corresponding branch.
Contrarily, with the definition of liInstr_t as it stands, observing a constructor of a value of type ('a,'b) liInstr_t brings no information on the type ('a,'b) liInstr_t. Consequently, the function ft1 of type type a b. (a,b) liInstr_t -> b is is promising to return a float array when called with ft1 (LiExecTRY:('a,float array) li_Instr_t). More generally, a function of type a b. (a,b) liInstr_t -> awhere no constructor impose a constraint onbis necessarily returning some value of typebthat was contained inside(a,b) liInstr_t` (or is not returning).
Using that knowledge, we can update your type liInstr_t to make the function ft1 works by adding the equations corresponding to the expected return type for ft1 to the definition of the type:
type liLabel_t = string
and context_t = string
and 'a context_list_t = 'a list
and 'a liChooser_t = 'a -> int
and ('a, 'b, 'ft1) liInstr_t =
| LiExec: 'a -> ('a, 'b,'a) liInstr_t (* ft1 returns the argument of LiExec *)
(* ft1 returns a string in all other cases *)
| LiExecTRY: ('a, 'b, string) liInstr_t
| LiLab: liLabel_t -> ('a, 'b, string) liInstr_t
| LiLabTRY: (liLabel_t, 'b, string) liInstr_t
| LiSeq: 'a liChooser_t * 'b list -> ('a,'b, string) liInstr_t
| LiAlt: 'a liChooser_t * 'b list -> ('a,'b, string) liInstr_t
| LiLoop: 'a liChooser_t * 'b list -> ('a,'b, string) liInstr_t
| LiName: 'a liChooser_t * liLabel_t * 'b context_list_t ->
('a,'b, string) liInstr_t
| Err_LiInstr: ('a, 'b, string) liInstr_t
| Nil_LiInstr: ('a, 'b, string) liInstr_t
and now that we have the right equation in place, we can define ft1 as:
let ft1: type a b c. (a, b, c) liInstr_t -> c = function
| LiExec n -> n
| LiExecTRY -> "4"
| LiLab s -> "LiLab"
| LiLabTRY -> "LiLabTRY"
| LiSeq (f, il) -> "LiSeq"
| LiAlt (f, il) -> "LiAlt"
| LiLoop (f, il) -> "LiLoop"
| LiName (f, il, ic) -> "LiName"
| Err_LiInstr -> "Err_LiInstr"
| Nil_LiInstr -> "Nil_LiInstr"
which typechecks without any error.
Ok, sorry in advance this probably is repost but I spent a good 30 minutes searching stackoverflow and couldn't find something similar enough to understand. Basically, I don't understand why
fn (f,g,x) => g(f(x))
gives the type
(’a -> ’b)*(’b -> ’c)*’a -> ’c
from my understanding it should start with
'a * 'b * 'c...
But this is evidently wrong.
Thanks in advance.
Indeed, it all starts with 'a * 'b * 'c, where f : 'a, g : 'b, x : 'c but then the type inference mechanism sees that f is applied x, so
so it concludes that the function f : 'd -> 'e (i.e. 'b = 'd -> 'e). Also, the type of x must comply with f's input type, thus 'd = 'c and f : 'c -> 'e.
Furthermore, g is a function as well, so 'b = 'y -> 'z and it's easy to see that f's output type must be equal to g's input type, which gives us the following type equation 'y = 'e.
We have x : 'c, f : 'c -> 'e, g : 'e -> 'z. Concrete variable names don't matter and we can rename them like so:
'c -> 'a
'e -> 'b
'z -> 'c
That gives as x : 'a, f : 'a -> 'b, and g : 'b -> 'c
Since fn (f,g,x) takes a triple as its input, it must have the type
<type of f> * <type of g> * <type of x> -> <output type of g>
Expanding the above semi-formal description we get
('a -> 'b) * ('b -> 'c) * a' -> 'c
After writing this piece of code
module type TS = sig
type +'a t
end
module T : TS = struct
type 'a t = {info : 'a list}
end
I realised I needed info to be mutable.
I wrote, then :
module type TS = sig
type +'a t
end
module T : TS = struct
type 'a t = {mutable info : 'a list}
end
But, surprise,
Type declarations do not match:
type 'a t = { mutable info : 'a list; }
is not included in
type +'a t
Their variances do not agree.
Oh, I remember hearing about variance. It was something about covariance and contravariance. I'm a brave person, I'll find about my problem alone!
I found these two interesting articles (here and here) and I understood!
I can write
module type TS = sig
type (-'a, +'b) t
end
module T : TS = struct
type ('a, 'b) t = 'a -> 'b
end
But then I wondered. How come that mutable datatypes are invariant and not just covariant?
I mean, I understand that an 'A list can be considered as a subtype of an ('A | 'B) list because my list can't change. Same thing for a function, if I have a function of type 'A | 'B -> 'C it can be considered as a subtype of a function of type 'A -> 'C | 'D because if my function can handle 'A and 'B's it can handle only 'A's and if I only return 'C's I can for sure expect 'C or 'D's (but I'll only get 'C's).
But for an array? If I have an 'A array I can't consider it as a an ('A | 'B) array because if I modify an element in the array putting a 'B then my array type is wrong because it truly is an ('A | 'B) array and not an 'A array anymore. But what about a ('A | 'B) array as an 'A array. Yes, it would be strange because my array can contain 'B but strangely I thought it was the same as a function. Maybe, in the end, I didn't understand everything but I wanted to put my thoughts on it here because it took me long to understand it.
TL;DR :
persistent : +'a
functions : -'a
mutable : invariant ('a) ? Why can't I force it to be -'a ?
I think that the easiest explanation is that a mutable value has two intrinsic operations: getter and setter, that are expressed using field access and field set syntaxes:
type 'a t = {mutable data : 'a}
let x = {data = 42}
(* getter *)
x.data
(* setter *)
x.data <- 56
Getter has a type 'a t -> 'a, where 'a type variable occurs on the right-hand side (so it imposes a covariance constraint), and the setter has type 'a t -> 'a -> unit where the type variable occurs to the left of the arrow, that imposes a contravariant constraint. So, we have a type that is both covariant and contravariant, that means that type variable 'a is invariant.
Your type t basically allows two operations: getting and setting. Informally, getting has type 'a t -> 'a list and setting has type 'a t -> 'a list -> unit. Combined, 'a occurs both in a positive and in a negative position.
[EDIT: The following is a (hopefully) clearer version of what I wrote in the first place. I consider it superior, so I deleted the previous version.]
I will try to make it more explicit. Suppose sub is a proper subtype of super and witness is some value of type super which is not a value of type sub. Now let f : sub -> unit be some function which fails on the value witness. Type safety is there to ensure that witness is never passed to f. I will show by example that type safety fails if one is allowed to either treat sub t as a subtype of super t, or the other way around.
let v_super = ({ info = [witness]; } : super t) in
let v_sub = ( v_super : sub t ) in (* Suppose this was allowed. *)
List.map f v_sub.info (* Equivalent to f witness. Woops. *)
So treating super t as a subtype of sub t cannot be allowed. This shows covariance, which you already knew. Now for contravariance.
let v_sub = ({ info = []; } : sub t) in
let v_super = ( v_sub : super t ) in (* Suppose this was allowed. *)
v_super.info <- [witness];
(* As v_sub and v_super are the same thing,
we have v_sub.info=[witness] once more. *)
List.map f v_sub.info (* Woops again. *)
So, treating sub t as a subtype of super t cannot be allowed either, showing contravariance. Together, 'a t is invariant.
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.