Arbitrary polymorphic variants as parameters

Hi fellow OCaml’ers! Long time reader, first time poster. :slight_smile:

I’m making a rookie mistake with polymorphic variants, but I can’t quite understand why. I figure others might stumble into this, so here goes…

First, what works: I’m adopting ('a, [> common_errors ]) result return values for error management. It allows error variants to aggregate as we go back towards the top level, without having to maintain a program-wide regular variant. (I’m not interested in exceptions for my ultimate use case.) The [> … ] bit in my function signatures is accepted. (The same in type signatures however, is a no-go and I need i.e. 'a instead. “Weird,” I thought.)

What doesn’t work: I’m creating an in-memory cache for information that is expensive to create or fetch. In order to add some maintenance code and layer this ahead of other storage mechanisms later, I thought of a process-wide cache which could accept anything. A polymorphic hash table achieves this in utop:

open Base
let store = Hashtbl.Poly.create ()
let () = Hashtbl.set store ~key:(`Foo 42) ~data:"asdf"

Compilation fails however:

Error: The type of this expression, (_[> `Foo of int ], string) Hashtbl.t,
       contains type variables that cannot be generalized

…that pesky underscore. :thinking: What’s more, this simpler experiment does compile:

let f (i : [> ]) = match i with _ -> 1
let _ = f `Roger
(* f has type ([> ] as 'a) -> int  *) *)

I’m pretty sure I’ll need to go a completely different route (one cache per key/value type closer to modules making use of it) but before I abandon this idea, I’d love to understand what I’m missing here, which seems fundamental to manipulating polymorphic variants.

Any brave soul willing to help a confused rookie? :wink:

1 Like

Maybe extensible variants would be a good fit for your use case, where you’re currently using polymorphic variants. It’s like having your own exn type. They were introduced in OCaml 4.02, see


This is usually because you didn’t provide a .mli file for your module and the compiler can’t work out what the type should be. For example, is it OK if I store a `Bar key in your table?

The problem is that an hashtbl is mutable, and it is not possible to export a polymorphic mutable value. Otherwise, it would be possible to have contradictory point of views on the type of the exported values in different
modules, which could lead to runtime crashes.

The solution is thus to either use an immutable data structure, or
close the type of the hash table in the implementation file (relying on the mli file is brittle and is not guaranteed to work):

open Base
let store: ([ `Foo | `Maybe_bar ], string) Hashtbl.t =
  Hashtbl.Poly.create ()
let () = Hashtbl.set store ~key:(`Foo 42) ~data:"asdf"
1 Like

Merci beaucoup @mjambon. :smiley: Since I don’t need to pattern match on those variants, just store and retrieve them in the hash without knowing about them, extensible seems perfect. I should turn to that section of the manual more often. I created a quick test and it does behave as expected: other modules are free to add their own constructors to my module’s key and data variants, so I can keep my module generic.

@talex5: You’re right that I hadn’t created an .mli, but I still can’t accept arbitrary polymorphic variants with one defined:

(* store_test.mli *)
val store : ([> ], string) Hashtbl.t


Error: The implementation experimental/
       does not match the interface experimental/.store_test.eobjs/byte/store_test.cmi:
       Values do not match:
         val store : (_[> `Foo of int ], string) Base.Hashtbl.t
       is not included in
         val store : ([>  ], string) Hashtbl.t

So I don’t think that polymorphic variants can be as open-ended as I’d need for this use. I’ve been working with dynamically typed languages for too long. :wink:

@octachron: Thank you. Since I want the performance characteristics of a hash table, this seems to confirm that extensible variants are my best choice.