17

I was browsing ocaml's standard library, and came across this code in the map.ml file.

module type S =
  sig
    type key
    type +'a t
    val empty: 'a t'

I'm wondering why there is type +'a t, and why the author use it instead of simply 'a t.
Its behaviour is strange and I can't deduce the usage of it.

# type +'a t = 'a list;;
type 'a t = 'a list
# type +'a t = +'a list;;
Characters 13-14:
  type +'a t = +'a list;;
               ^
Error: Syntax error

Thanks

octref
  • 6,521
  • 7
  • 28
  • 44

2 Answers2

17

To build up on Jeffrey's answer, the reason the developers did the work of marking the abstract type as covariant here is likely not to help you use subtyping (essentially nobody uses subtyping in OCaml, as parametric polymorphis is generally preferred), but to use an even-less-well-known aspect of the type system called the "relaxed value restriction", thanks to which covariant abstract types allow more polymorphism.

You may safely ignore those subtleties until, someday, you hit a problem with an abstract type of yours that is not as polymorphic as you would like, and then you should remember than a covariance annotation in the signature may help.

We discussed this on reddit/ocaml a few months ago:

Consider the following code example:

module type S = sig
  type 'a collection
  val empty : unit -> 'a collection
end

module C : S = struct
  type 'a collection =
    | Nil
    | Cons of 'a * 'a collection
  let empty () = Nil
end

let test = C.empty ()

The type you get for test is '_a C.collection, instead of the 'a C.collection that you would expect. It is not a polymorphic type ('_a is a monomorphic inference variable that is not yet fully determined), and you won't be happy with it in most cases.

This is because C.empty () is not a value, so its type is not generalized (~ made polymorphic). To benefit from the relaxed value restriction, you have to mark the abstract type 'a collection covariant:

module type S = sig
  type +'a collection
  val empty : unit -> 'a collection
end

Of course this only happens because the module C is sealed with the signature S : module C : S = .... If the module C was not given an explicit signature, the type-system would infer the most general variance (here covariance) and one wouldn't notice that.

Programming against an abstract interface is often useful (when defining a functor, or enforcing a phantom type discipline, or writing modular programs) so this sort of situation definitely happens and it is then useful to know about the relaxed value restriction.

If you want to understand the theory, the value restriction and its relaxation are discussed in the 2004 research article Relaxing the value restriction from Jacques Garrigue, whose first few page are a rather interesting and accessible introduction to the topic and main idea.

kosmikus
  • 19,549
  • 3
  • 51
  • 66
gasche
  • 31,259
  • 3
  • 78
  • 100
  • Thanks for your detailed explanation. I came back to map.ml and found the following line: type 'a t = Empty | Node of 'a t * key * 'a * 'a t * int which is very much like your examples. However, can you explain more about monomorphic inference type? It seems that '_a C.collection still matches 'a C.collection. If the type of C.empty() is not generalized, when and what problem will it cause? – octref Mar 09 '13 at 15:46
  • 2
    @octref: try to use the function `let id = (fun x -> x) (fun x -> x)`, which has type `'_a -> '_a` (because its definition is an application rather than a value, and its type variable occurs in both positive and negative position). You will quickly see the problem: you cannot use it at two different types, so it is not polymorphic. – gasche Mar 09 '13 at 16:43
  • Thanks gasche, this is really good to know about. I imagined it was only about ordinary old subtyping (which I almost never use, now that you mention it). – Jeffrey Scofield Mar 09 '13 at 16:44
  • Thanks gasche, now I understand it. – octref Mar 10 '13 at 14:16
13

This marks the type as covariant with respect to the module type. Assume you have two maps whose keys are the same type. This + says if the values of one map A are of a subtype of the values of the other map B, then the overall type of map A is a subtype of map B's type. I found a pretty good description of this in the Jane Street blog.

tdrd
  • 7
  • 3
Jeffrey Scofield
  • 65,646
  • 2
  • 72
  • 108
  • If map A is a subtype of map B's type, then does that mean I would be able to use the method of B on A? Does it resemble the subclass notion in Java? – octref Mar 09 '13 at 16:03
  • 1
    The non-OO part of OCaml doesn't resemble Java very much at all. Java subclassing *is* a kind of subtyping, but (I believe gasche here) the use of subtyping is rare in OCaml while subclassing is a key feature of Java. Note that OCaml doesn't infer subtyping. You need to explicitly coerce the type of an expression to a supertype. This is fine because it almost never comes up. However, when you do this, then yes, it allows functions defined for the supertype (not just methods, all functions) to be applied to the subtype. – Jeffrey Scofield Mar 09 '13 at 16:39