I’m playing around with GADTs and running in to some puzzling errors I’m trying to understand better. Here’s a simple, working example of a function that uses GADTs as a way of controlling its return type.
module If_nothing_chosen = struct
type (_, _) t =
| Default_to : 'a -> ('a, 'a) t
| Raise : ('a, 'a) t
| Return_none : ('a, 'a option) t
end
let choose_one (type a b) test things (if_nothing_chosen : (a, b) If_nothing_chosen.t) =
let rec loop (things : a list) : b =
match things with
| [] ->
(match if_nothing_chosen with
| Raise -> failwith "No matching item found"
| Return_none -> None
| Default_to x -> x)
| thing :: rest ->
if test thing
then (
match if_nothing_chosen with
| Default_to _ -> thing
| Raise -> thing
| Return_none -> Some thing)
else loop rest
in
loop things
;;
This compiles just fine, but if I want to define choose_one
itself as a recursive function, it doesn’t compile.
let rec choose_one
(type a b)
(test : a -> bool)
(things : a list)
(if_nothing_chosen : (a, b) If_nothing_chosen.t)
: b
=
match things with
| [] ->
(match if_nothing_chosen with
| Raise -> failwith "No matching item found"
| Return_none -> None
| Default_to x -> x)
| thing :: rest ->
if test thing
then (
match if_nothing_chosen with
| Default_to _ -> thing
| Raise -> thing
| Return_none -> Some thing)
else choose_one test rest if_nothing_chosen
;;
The error message I get is:
File "src/w.ml", line 104, characters 20-24:
104 | else choose_one test rest if_nothing_chosen
^^^^
Error: This expression has type a -> bool
but an expression was expected of type 'a
The type constructor a would escape its scope
which I find pretty confusing! Given that there’s a recursive use of the function, I thought doing explicit polymorphic recursion might fix things, and indeed it did.
let rec choose_one : type a b. (a -> bool) -> a list -> (a, b) If_nothing_chosen.t -> b =
fun test things if_nothing_chosen ->
match things with
| [] ->
(match if_nothing_chosen with
| Raise -> failwith "No matching item found"
| Return_none -> None
| Default_to x -> x)
| thing :: rest ->
if test thing
then (
match if_nothing_chosen with
| Default_to _ -> thing
| Raise -> thing
| Return_none -> Some thing)
else choose_one test rest if_nothing_chosen
;;
But I don’t really understand why it was necessary, given that the recursive call is happening at the same type. Also, I don’t fully understand why there’s a “type constructor would escape its scope” error in the first place.
y