Reputation: 237
I am encoding natural numbers as a difference list:
type z = Z
type +'a s = S
type _ nat =
| Z : ('l * 'l) nat
| S : ('l * 'm) nat -> ('l * 'm s) nat
This allows me to encode addition easily :
let rec add : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
fun i1 i2 -> match i2 with
| Z -> i1
| S i -> S (add i1 i) (* OK *)
However the following variant don't typecheck:
let rec add2 : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
fun i1 i2 -> match i2 with
| Z -> i1
| S i -> add2 (S i1) i (* KO *)
Does anybody know how to make it right, i.e. having an efficient typed add, possibly changing the Nat type ?
Note that this question is more general than just the Nat addition: with more complicated sized type, the same question arrise. E.g. with sized list, all the accumlator-based function like rev_append are hard to type.
Upvotes: 3
Views: 248
Reputation: 12113
The problem here is that S i1
has type (l s * m s) nat
whilst i
has type m * n
. The recursive call to add2
is therefore ill-typed: add2
expect the rightmost index of its first argument to match the leftmost one of its second and m s
is most definitely different from m
.
Because you are encoding a value as a difference, you can easily fix that: you can notice that (l * m) nat
and (l s * m s) nat
should be the same thing. And you can indeed define a function shift
turning one into the other which is basically an identity function:
let rec shift : type l m. (l*m) nat -> (l s * m s) nat = function
| Z -> Z
| S i -> S (shift i)
You can then adjust the type of i
in the recursive call to add2
to have the whole thing typecheck:
let rec add2 : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
fun i1 i2 -> match i2 with
| Z -> i1
| S i -> add2 (S i1) (shift i)
Edit: getting rid of the non tail-recursive shift
.
A common technique to turn a function which is not tail-recursive into a tail recursive one is to define it in Continuation Passing Style: the function takes an extra argument which describes what to do with the returned value
We can turn shift
into a tail-recursive function shift3
like so:
let shift3 : type l m. (l*m) nat -> (l s * m s) nat =
let rec aux : type l m c. ((l s * m s) nat -> c) -> (l * m) nat -> c =
fun k i -> match i with
| Z -> k Z
| S j -> aux (fun i -> k (S i)) j
in fun i -> aux (fun x -> x) i
Which then lets us define add3
:
let rec add3 : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
fun i1 i2 -> match i2 with
| Z -> i1
| S i -> add3 (S i1) (shift3 i)
One (easy but dirty) way of getting rid of the non tail-recursive shift
function is to replace it with Obj.magic
: indeed, as mentioned before, our shift
s are nothing but structurally defined identity functions.
This leads us to:
let shift4 : type l m. (l*m) nat -> (l s * m s) nat = Obj.magic
let rec add4 : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
fun i1 i2 -> match i2 with
| Z -> i1
| S i -> add4 (S i1) (shift4 i)
Upvotes: 1