The Base Module and Polymorphic Functions

So, I’m testing some aspects of the language (as a beginner), and I cross with something particularly interesting to me.
While seeing about polymorphism functions I have the following polymorphic function:

(* A function to remove duplicating elements from a list *)
let rec delDuplicates =
function
    | [] -> []
    | x :: [] -> x :: []
    | x :: y :: rest ->
        if x = y then delDuplicates (y :: rest)
        else x :: delDuplicates (y :: rest);;

This is a function to remove duplicate entries in a list. The compiler says it has the type:

val delDuplicates : 'a list -> 'a list = <fun>

So, I can evaluate the following cases:

delDuplicates [1; 1; 1; 2; 3; 3];;

delDuplicates ["A"; "A"];;

And I get the expected result from these two evaluations.

Interesting enough, if I call the Base module this function turns to a polymorphic to a non-polymorphic one as it now has the type:

val delDuplicates : int list -> int list = <fun>

So, evaluating delDuplicates ["A"; "A"];; gives me a type error.

This happens only if I use the Base module.

Someone can help me understand what is happening here? I plan to implement some mathematical notions in OCaml and it would be nice to have some functions from the base module and still have polymorphism for my functions.

I have called the module in this way:

#require "base";;
open Base;;

This is the same issue that was mentioned in Possible bug in Base.List.map. In short, the standard (=) operator is polymorphic, but Base redefines it to be only defined for ints, since polymorphic compare is generally not what you want. See also: Removing polymorphic compare from Core.

3 Likes

As @mc10 said, this is due to Base redefining (=) to hide the standard (=) function.

Here is what you can do:

  1. Do not actually open Base. This way you keep the standard polymorphic operator. Note that there are reasons to not do that, as mentioned in the discussion @mc10 linked. If you want to call functions from Base without opening it, you can use their qualified names; for instance Base.List.map.

  2. If you do open Base, you can still refer to the polymorphic operator as Poly.(=) (and there is also Poly.equal). So for instance in your function you could have : if Poly.equal x y then ...
    Or: if Poly.(x = y) then ... (this is called a local open)

  3. One way to not use polymorphic comparison at all is to parametrise your code over the equality function. The simplest way to go about it is to have any function that has to be polymorphic and do equality tests take the equality function as an argument. For instance your function becomes :

let rec delDuplicates equal = function
   ...
       if equal x y then ...

Then your function has type:

val delDuplicates : ('a -> 'a -> bool) -> 'a list -> 'a list

And you call it as:

delDuplicates Int.equal [1; 1; 1; 2; 3; 3]
1 Like

Just to add to @threepwood’s answer: I would recommend making equal a named parameter. This will make your code self-documenting and much more readable.

 let delDuplicates ~equal = ...

EDIT: Yet another good pattern, if you’re not too concerned about safety, is to make equal an optional parameter that defaults to polymorphic compare:

let delDuplicates ?(equal=Poly.equal) = ...

This gives you both a self-documenting signature,

val delDuplicates : ?equal:('a -> 'a -> bool) -> 'a list -> 'a list = <fun>

as well as the convenience to write

delDuplicates [1; 1; 2; 2; 3]

as before. Plus you have the flexibility to do something like this:

delDuplicates ~equal:(fun x y -> x%2 = y%2) [1; 3; 5; 7]
1 Like

That is exactly what I need for my implementation. Thank you all for your answers.