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 bestring
(i.e.,LiLabel_t
) except maybe for the first variant, before being rejected later for being less general than the return typeb
. 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
The first branch implies that
'a
=int
. Then the second branchB
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 functionis well typed by unifying
'a
withstring
, giving the type(string,'b) t -> string
took
. But if you ask the typechecker to check that the type is polymorphic, it will notify you that the type ofok
is not polymorphic: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:
Question 3. Yes, it is possible, but this is uncommon occurrence
Most use cases are far better served by the shorthand syntax:
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.