I think I have a problem that’s relatd to higher-kinded-types, but I’m not sure.
First attempt:
Consider the following problem. Say I have a function that shuffles an array:
let shuffle_array (arr : 'e array) : unit =
for i = Array.length arr - 1 downto 1 do
let j = Random.int (i + 1) in
let tmp = arr.(i) in
arr.(i) <- arr.(j);
arr.(j) <- tmp
done
Now I want create similar functions shuffle_list and shuffle_seq that work on lists and sequences, respectively. I tried this:
let shuffle_convert
(input_conv : 'i -> 'e array)
(output_conv : 'e array -> 'o)
(input : 'i)
: 'o =
let arr = input_conv input in
shuffle_array arr;
output_conv arr
But it doesn’t work as wanted, because if I pass Array.of_list and Array.to_list as conversion functions to shuffle_convert, I won’t get a polymorphic function:
let shuffle_list_fails : 'a. 'a list -> 'a list =
shuffle_convert Array.of_list Array.to_list
Error: This definition has type 'a list -> 'a list which is less general than
'a0. 'a0 list -> 'a0 list
I tried something like this, but it also doesn’t work (not even syntactically):
let shuffle_convert_higher
(input_conv : 'e 'i -> 'e array)
(output_conv : 'e array -> 'e 'o)
(input : 'e 'i)
: 'e 'o =
let arr = input_conv input in
shuffle_array arr;
output_conv arr
Error: Syntax error: ')' expected, the highlighted '(' might be unmatched
A weak version works:
I can create a weak version of the function:
let shuffle_list_weak : 'a list -> 'a list =
shuffle_convert Array.of_list Array.to_list
In utop it gets shown as:
val shuffle_list_weak : '_a list -> '_a list = <fun>
I don’t understand why it is denoted as '_a and not '_weak1, but it does behave like a weak type variable.
utop # shuffle_list_weak [1;2;3;4;5];;
- : int list = [4; 2; 1; 3; 5]
utop # shuffle_list_weak ["A";"B";"C"];;
Error: This constant has type string but an expression was expected of type int
So this is not what I want.
Practical solution (with boilerplate):
Note that it is possible to create the functions I want with a bit of boilerplate (i.e. when I don’t use shuffle_convert):
let shuffle_list_boilerplate : 'a. 'a list -> 'a list =
fun input ->
let arr = Array.of_list input in
shuffle_array arr;
Array.to_list arr
let shuffle_seq_boilerplate : 'a. 'a Seq.t -> 'a Seq.t =
fun input ->
let arr = Array.of_seq input in
shuffle_array arr;
Array.to_seq arr
Thus, I do have a practical solution. But I would like to understand the underlying problem better.
Using modules as a workaround (that sometimes fails):
I did some searches and was wondering if this is related to higher-kinded types (not sure if I even understand what that is), but apparently OCaml doesn’t have them but there is a work-around for it using modules.
As far as I understand, the problem in shuffle_convert as written above is that the types 'i and 'o should rather be type constructors, but I can’t write 'e 'i and 'e 'o as types because the compiler expects type variables to refer to concrete types and not to type constructors, right?
So I tried to work around this issue using modules:
module type Container = sig
type 'a t
val to_array : 'a t -> 'a array
val of_array : 'a array -> 'a t
end
module Shuffle_convert (C : Container) = struct
let f : 'a. 'a C.t -> 'a C.t =
fun input ->
let arr = C.to_array input in
shuffle_array arr;
C.of_array arr
end
This compiles, YAY!
(Even with the explicit polymorphic annotation for f, which is what I want.)
But, apart from the bloated syntax, I was disappointed when I tried this:
let shuffle_list_weak_again =
let module Shuffle_list = Shuffle_convert (struct
type 'a t = 'a list
let to_array = Array.of_list
let of_array = Array.to_list
end) in
Shuffle_list.f;;
val shuffle_list_weak_again : '_weak1 list -> '_weak1 list = <fun>
Why is shuffle_list_weak_again weak?
If I instantiate the module outside of the function, it works:
module Shuffle_list = Shuffle_convert (struct
type 'a t = 'a list
let to_array = Array.of_list
let of_array = Array.to_list
end);;
let shuffle_list = Shuffle_list.f
module Shuffle_seq = Shuffle_convert (struct
type 'a t = 'a Seq.t
let to_array = Array.of_seq
let of_array = Array.to_seq
end);;
let shuffle_seq = Shuffle_seq.f
module Shuffle_list : sig val f : 'a list -> 'a list end
val shuffle_list : 'a list -> 'a list = <fun>
module Shuffle_seq : sig val f : 'a Seq.t -> 'a Seq.t end
val shuffle_seq : 'a Seq.t -> 'a Seq.t = <fun>
Questions:
My open questions:
- Is my approach of
shuffle_convert_highertheoretically correct (just not supported by OCaml)? - Why does
shuffle_list_weaknot show'_weak1in its type signature but'_a? Is the word “weak” noise here and can be omitted? When is it omitted? - What is the practical approach to the problem? Simply avoiding it by skipping the step of a generalized
shuffle_convertand directly write each function? - Why is
shuffle_list_weak_againweak andshuffle_listnot? Both seem to be defined in the same way, except thatshuffle_list_weak_againuses a locally instantiated functor. Is this a bug? Or am I missing something?
Also, thanks again for all your responses on this forum so far.
I feel like l learned a lot here already.