Hi !
I wonder why
let skip_none =
let rec skip_none acc_l = function
| None :: tl -> skip_none acc_l tl
| Some e :: tl -> skip_none (e::acc_l) tl
| [] -> acc_l
in
skip_none []
gives following compiler error:
34 | let skip_none =
^^^^^^^^^
Error: The type of this expression, '_weak1 option list -> '_weak1 list,
contains type variables that cannot be generalized
while this:
let skip_none l =
let rec skip_none acc_l = function
| None :: tl -> skip_none acc_l tl
| Some e :: tl -> skip_none (e::acc_l) tl
| [] -> acc_l
in
skip_none [] l
compiles fine. (only variable “l” and the first and last line changes) It is not really a problem, I am just curious of how it works Do you have an idea ?
This is the value restriction at play.
In short the result of a function application may refer to some captured mutable state, and mutable states should not be polymorphic. For instance, if I define a function swap
like this
let store = ref None
let swap x = match !store with
| Some y -> store := Some x; y
| None -> store := Some x; x
subsequent arguments should have the same type.
To avoid this issue, the easiest solution is eta-expansion (transforming expr
into fun x -> expr x
). Indeed, if skip_none []
is syntactically the result of a computation and thus cannot be polymorphic, this is not the case of fun l -> skip_none [] l
since the function will only be evaluated once the argument l
is provided.
1 Like
It’s worth noting that function definitions like your original one are perfectly legal. What isn’t legal is exposing weak type variables in module types. I suspect the compiler is yelling at you because you have this code in a .ml file with no corresponding .mli file; the compiler tries to infer a module type, and cannot do so.
1 Like
Thanks a lot @octachron , your example is crystal clear, I feel I understand now.
@Levi_Roth, indeed what you say is correct, I exposed the function without mli file. Thanks for sharing that I could use this with a proper mli file.
Have both a good day !