简体   繁体   中英

OCaml type cannot be generalized

I have the following code:

type 'v state = {
  visited: string list;
  unvisited: string list;
  value: 'v;}

type ('a,'b) parser =
  | Parser of ('a state -> 'b state list)

type 'a result =
  | Ok of 'a
  | Err

let parseString x = Ok x
let parseInt x = Ok (int_of_string x)
let custom = fun stringToSomething  ->
    Parser (fun { visited; unvisited; value }  ->
            match unvisited with
            | [] -> []
            | next::rest ->
                 (match stringToSomething next with
                  | Ok (nextValue) ->
                      [{
                         visited = (next :: visited);
                         unvisited = rest;
                         value = (value nextValue)
                       }]
                  | Err  -> [])))

let stringp = custom parseString
let intp = custom parseInt

When I try to compile my program I get the following error on custom parseString :

Error: The type of this expression, (string -> '_a, '_a) parser,
         contains type variables that cannot be generalized

What does this error mean?

A general type variable is a variable that can be substituted with any type. Nongeneral type variables, that are also called weak type variables, are variables that can be concretized to one and only one type. Usually, weak type variables arise when a value is mutable or when it is a function application. In general, type generalization can be applied only to the expression that belongs to the class of “syntactic values”, which includes constants, identifiers, functions, tuples of syntactic values, etc . This general rule is relaxed in OCaml, and type variables of all other expressions can also be generalized if they occur in covariant positions. For this, the type system should either see the type definition and infer covariance from it (ie, the type shouldn't be abstract), or type variables should be restricted to a covariant type (ie, prefixed with + in the type definition).

Nongeneralizable types are ok inside a module structure, but they cannot escape a compilation unit, because this will break type soundness (different modules may concretize them to different values, and it is beyond type system capabilities to prevent this). Since your module doesn't contain .mli file, everything is exported by default.

In general, there are 4 ways to solve the problem:

  1. create .mli file, where a value with the nongeneralized type is either hidden or concretized to a monomorphic type;

  2. concretize the type variable to a monomorphic type using type constraint;

  3. generalize the type using eta-expansion, ie, translate a value, into a syntactic function;

  4. prove to the type system, that value restriction can be relaxed, by showing that the type variable is covariant.

These are general strategies. In your case, it wouldn't be possible to generalize stringp , as custom parseString doesn't belong to a class of syntactic values (it is a function application), and it is an expression where one of the type variables is contravariant, because it occurs on the left of the -> type operator. You can always ask a type system, about the variance of your type variables. For example, without knowing any variance rules, we may ask the type system: is it true the 'a and 'b are covariant.

type (+'a,+'b) parser =
  | Parser of ('a state -> 'b state list)

And the covariance inference algorithm will compute the following answer:

The 1st type parameter was expected to be covariant,
       but it is injective contravariant.

From the perspective of the type system, it means, that the definition of stringp as a side effect may access a value of type 'a (for example, to store it in the cache storage), and generalizing here the type variable is unsound, ie, it will lead to the segmentation fault.

So, the only solution left here is to eta-expand your definition to a function, this will guarantee to the type system, that every time a fresh parser is created, eg,

let stringp () = custom parseString

Or, alternatively, do not create values of type parser, but just provide a user with the combinator to create them (ie, the custom function). So that they basically can create them on the fly, in the contexts, where generalization is not required), eg,

let parse = custom
let string = parseString

let my_parser ... = 
  parse string >> parse char >> ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM