2

Is there a way to have a recursive call but with different type parameters?

Here's an example which I think should compile, but doesn't.

let swap (a, b) = (b, a)

module Test : sig
  val test : bool option ->
  ('a -> 'b -> 'c) * ('b -> 'a -> 'c)
  -> 'a -> 'b -> 'c
end = struct
  let rec test (b : bool option)
      (f : ('a -> 'b -> 'c) * ('b -> 'a -> 'c))
      (x : 'a) (y : 'b) : 'c =
    match b with
    | None -> (fst f) x y
    | Some true -> (test None f x y)
    | Some false -> (test None (swap f) y x)
end

The compiler insists that 'a and 'b must be the same type in test even though there's no reason for it. Is this a bug?

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
dspyz
  • 5,280
  • 2
  • 25
  • 63

4 Answers4

6

This is actually an very interesting question.

I didn't know the answer before, but by reading through the two answers before me, plus a bit research, I will try explain and answer below.


Basically, what you want to achieve is the signature like this

val test : bool option -> ('a -> 'b -> 'c) * ('b -> 'a -> 'c) -> 'a -> 'b -> 'c

First of all, I have to emphasise that trying to force the parameters to be different polymorphic types, i.e., use 'a, 'b, 'c etc, will not necessarily force OCaml compiler to think that the parameters must have different types.

for example, if you do

let f (x:'a) (y:'b) = x + y;;

It seems you are forcing x and y to be different types, but after compiling, it gives

val f : int -> int -> int = < fun >

Basically, OCaml will anyway do its type inference, and apply the conclusion if it is not just against the forced type.

You may think the 'a and 'b in let f (x:'a) (y:'b) = x + y;; to be maybe x and y will have different types and also possibly same types. So it is pointless to force types of parameters like that, right?


So, let's remove all the types forced on parameters and we get

let rec test b f x y =
    match b with
    | None -> (fst f) x y
    | Some true -> test None f x y
    | Some false -> test None (swap f) y x

The type of test will be given by OCaml like this:

val test : bool option -> ('a -> 'a -> 'b) * ('a -> 'a -> 'b) -> 'a -> 'a -> 'b = < fun >

So, basically, OCaml thinks x and y must have the same types, and c is not there because the next available type tag for OCaml to use is 'b.

Why x and y must have same types?

When OCaml meets let rec test b f x y, ok, it will think x has type of 'a and y has type of 'b'.

When OCaml meets | Some true -> test None f x y, no problem, the above type inference still stand because you are pass same x and y to test again.

The the funny part is when OCaml meets | Some false -> test None (swap f) y x. You are trying pass y and x (notice the order) to test. In order to let test work, x and y must have the same type, right?


Basically, above is what the counter side of polymorphism recursion @Jeffrey Scofield answered.

polymorphism recursion means, a function can have some parameters whose types can be changed during recursion, instead of stay constant.

OCaml by default of course only allow constant parameter types.


So how does rank 2 polymorphism work?

So how to solve it?

You need a for_all type 'a.. have a look at my question: In OCaml, what type definition is this: 'a. unit -> 'a.

If we use 'a. or 'a 'b. in type definition, then it means it is really for all types, real polymorphic types, and please, ocaml, do not narrow them down as long as it does not harm.

let rec test : 'a 'b. bool option -> ('a -> 'b -> 'c) * ('b -> 'a -> 'c) -> 'a -> 'b -> 'c =
  fun b f x y ->
    match b with
    | None -> (fst f) x y
    | Some true -> test None f x y
    | Some false -> test None (swap f) y x

Above is the new version of test.

You force type with 'a 'b. for the function test and for 'a and 'b, ocaml will think they are really both polymorphic, and thus the parameter x and y can be accepted in both orders.

Community
  • 1
  • 1
Jackson Tale
  • 25,428
  • 34
  • 149
  • 271
  • Afterall I think I appreciate your candidness. – didierc May 14 '14 at 01:28
  • @didierc I wrote it lengthy as I want to keep a record for myself – Jackson Tale May 14 '14 at 09:12
  • While your answer is mostly correct, this is _not_ rank 2 polymorphism. It's still ordinary rank 1 polymorphism, but with polymorphic recursion enabled through an explicit type annotation. – Andreas Rossberg May 14 '14 at 18:44
  • @AndreasRossberg oh, really? I took that blog post. Could you write an answer to further explain rank 2 in OCaml or polymorphic recursion? – Jackson Tale May 15 '14 at 08:31
  • @JacksonTale, rank 2 polymorphism describes function types with polymorphic _arguments_ (at depth 2). For example, (forall 'a. 'a -> 'a) -> int. See http://stackoverflow.com/questions/22362196/what-is-n-in-rankntypes/22362474#22362474 for a more precise definition. Fortunately, that's not needed for polymorphic recursion. – Andreas Rossberg May 15 '14 at 12:13
  • @AndreasRossberg ok. So you mean what I did is just to enable polymorphic recursion, but not rank-2? I got confused a bit. So rank-2 polymorphisim is just one of the parameter is a function and inside that function, polymorphism is there? – Jackson Tale May 15 '14 at 13:08
  • As Andreas says that is not rank-2 polymorphism. I think that you are instead refering to [explicit polymorphic type annotations](http://caml.inria.fr/pub/docs/manual-ocaml-4.00/manual021.html#toc79) which are indeed required for polymorphic recursion. – Leo White May 15 '14 at 16:05
  • @JacksonTale, yes, that's right. (To be precise, the polymorphic parameter doesn't need to be a function itself, e.g. (forall 'a. 'a list) -> int would be rank 2 as well.) – Andreas Rossberg May 15 '14 at 17:34
  • @AndreasRossberg could you please give me an example of rank 2 function definiation? I mean with implementation, not just type. and explain more? – Jackson Tale May 15 '14 at 19:31
  • @LeoWhite could you please give me an example of rank 2 function definiation? I mean with implementation, not just type. and explain more? – Jackson Tale May 15 '14 at 19:34
  • @JacksonTale, simple examples: `fun (l : forall 'a. 'a list) -> (3::l, true::l)` or `fun (f : forall 'a. 'a -> 'a) -> (f 3, f true)` (neither of course is legal OCaml, but you could emulate it using records with polymorphic fields). I'm not sure what other explanation you are looking for specifically, there isn't really much more to it. – Andreas Rossberg May 16 '14 at 14:49
4

You're asking for polymorphic recursion, for which type inference is undecidable in the general case: Wikipedia on Polymorphic Recursion. So I don't think it's a bug.

I think there are ways to get what you want using rank-2 polymorphism.

Jeffrey Scofield
  • 65,646
  • 2
  • 72
  • 108
  • Oh. Curses! Foiled again by undecidability. Except that the Wikipedia article says it's undecidable "without programmer-supplied type-annotations" so rather than assuming the wrong type, shouldn't the Ocaml compiler just require the user to give the type explicitly when it sees a case like this? – dspyz May 13 '14 at 20:31
  • Right, that's what you can do with rank-2 polymorpic types (I think). I'd like to look into it, but am pressed for time right now :-( – Jeffrey Scofield May 13 '14 at 20:33
  • I googled "Ocaml rank-2 types" and didn't find anything official-looking. Is there another term for it? – dspyz May 13 '14 at 20:35
  • 1
    Try this link: [Polymorphic Recursion with Rank-2 Polymorphism](http://alaska-kamtchatka.blogspot.com/2010/08/polymorphic-recursion-with-rank-2.html). I might have time to figure it out later, or maybe I won't be able to figure it out :-) – Jeffrey Scofield May 13 '14 at 20:37
  • @JeffreyScofield, I'm not sure where the author of that blog post got the idea from that this is rank 2 polymorphism -- no rank 2 types occur anywhere. – Andreas Rossberg May 14 '14 at 18:46
  • Thanks, I was kind of taking his word for it. If you can help with polymorphic recursion that would be excellent. – Jeffrey Scofield May 14 '14 at 18:48
  • @JeffreyScofield, please see my comment to JacksonTale's answer. – Andreas Rossberg May 15 '14 at 12:16
1

I think it's a bug because if you create a new function (identical to the first one), then the type is correct:

# let rec t b f x y = match b with
  | true -> (fst f) x y
  | false -> u true (swap f) y x
  and u b f x y = match b with
  | true -> (fst f) x y
  | false -> t true (swap f) y x;;
val t : bool -> ('a -> 'b -> 'c) * ('b -> 'a -> 'c) -> 'a -> 'b -> 'c = <fun>
val u : bool -> ('a -> 'b -> 'c) * ('b -> 'a -> 'c) -> 'a -> 'b -> 'c = <fun>
Thomash
  • 6,339
  • 1
  • 30
  • 50
1

using OCaml 4.01.0:

module rec Test : sig 
  val test : bool option -> ('a -> 'b -> 'c) * ('b -> 'a -> 'c) -> 'a -> 'b -> 'c 
  end = struct
  let rec test b f x y =
    match b with 
       | None -> (fst f) x y
       | Some true -> (Test.test None f x y)
       | Some false -> (Test.test None (swap f) y x) 
end;;

See the section on Recursive modules.

Small example as a test case:

 let add_float_int x y = x +. (float y);;
 let add_int_float x y = add_float_int y x;;
 let adds = add_float_int, add_int_float;;
 List.map (fun x -> Test.test x adds 10 10.) [None; Some true; Some false];;
didierc
  • 14,572
  • 3
  • 32
  • 52