Polymorphic parameters
The polymorphic parameters extension allows you to have function parameters with polymorphic types. For example, a function could have a type like:
val f : ('a. 'a -> 'a list) -> int list
As a practical example, let’s consider the creation functions from
ppx_typed_fields
. Given a record definition:
type t =
{ a : string
; b : int
}
ppx_typed_fields
gives you a type representing the fields indexed by
their type:
type 'a field =
| A : string field
| B : int field
A useful function to provide for these types is one that can create a
t
if given a function that returns a value for each of the fields. You
might try to write that function like so:
let create f =
{ a = f A; b = f B }
but the compiler will complain about this:
Line 3, characters 21-22:
3 | { a = f A; b = f B };;
^
Error: This expression has type int field
but an expression was expected of type string field
Type int is not compatible with type string
The issue is that you need to apply f
to both a string field
and
an int field
: you need it to be polymorphic.
There are existing ways to work around this issue — polymorphic
record fields, first-class modules and polymorphic methods — but they
all require defining a fresh type to contain f
. These work-arounds
require additional code at each call site to wrap f
in the associated
type.
With polymorphic parameters, you can annotate f
as
polymorphic:
let create (f : 'a. 'a field -> 'a) =
{ a = f A; b = f B }
which gives create
the following type:
val create : ('a. 'a field -> 'a) -> t
It can be called on a suitable function directly:
let forty_two (type a) : a field -> a = function
| A -> "forty two"
| B -> 42
let r = create forty_two
Limitations
All polymorphic parameter require an annotation. Without an annotation OxCaml will assume that the parameter is monomorphic.
Rather than annotating f
directly, you can also annotate create
as
a whole:
let create : ('a. 'a field -> 'a) -> t =
fun f -> { a = f A; b = f B }
or rely on the expected type propagated from a function application:
let with_creator (c : ('a. 'a field -> 'a) -> t) =
c forty_two
let r = with_creator (fun f -> { a = f A; b = f B })
Type variables in OCaml always stand for monomorphic types. You cannot
instantiate a type like 'a -> 'b
to ('a. 'a field -> 'a) -> t
. In
practice, this means that functions like apply
:
let apply f x = f x
cannot be applied to functions with polymorphic parameters:
Line 1, characters 6-12:
1 | apply create forty_two;;
^^^^^^
Error: This expression has type ('a. 'a field -> 'a) -> t
but an expression was expected of type 'b -> 'c
The universal variable 'a would escape its scope
Similarly, applying a function with polymorphic parameters requires
knowing the type of the function. Without type information, OCaml will
assume that the function has type 'a -> 'b
. The rules for knowing
the type of the function are the same as those for record field
disambiguation. For example,
let with_create_mono c =
c forty_two
will get the type:
val with_create_mono : (('a field -> 'a) -> 'b) -> 'b
because OCaml has assumed that the parameter of c
is monomorphic,
which prevents it from being applied to create
:
Line 1, characters 17-23:
1 | with_create_mono create;;
^^^^^^
Error: This expression has type ('a. 'a field -> 'a) -> t
but an expression was expected of type ('b field -> 'b) -> 'c
The universal variable 'a would escape its scope
Note that these limitations only apply to parameters with genuinely polymorphic parameters. Given a type like:
type 'a t = int
the type ('a. 'a t) -> string
is completely equivalent to int ->
string
.