Проблема в том, что S i1
имеет тип (l s * m s) nat
, тогда как i
имеет тип m * n
. Таким образом, рекурсивный вызов add2
: add2
ожидает, что самый правый индекс его первого аргумента совпадает с самым левым вторым, а m s
определенно отличается от m
.
Поскольку вы кодируете значение как разницу, вы можете легко исправить это: вы можете заметить, что (l * m) nat
и (l s * m s) nat
должны быть одинаковыми. И вы действительно можете определить функцию shift
поворот одна в другую, которая в основном функция идентичности:
let rec shift : type l m. (l*m) nat -> (l s * m s) nat = function
| Z -> Z
| S i -> S (shift i)
Вы можете настроить тип i
в рекурсивном вызове add2
иметь всю вещь 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)
Редактировать: избавиться от не-хвост-рекурсивный shift
.
Продолжение трансформации Passing Стиль
Общий метод, чтобы включить функцию, которая не является хвостовой рекурсией в хвост рекурсивной один должен определить его в Continuation Passing Style: функция принимает дополнительный аргумент, который описывает, что делать с Возвращаемое значение
Мы можем превратить shift
в хвостовую рекурсию функции shift3
следующим образом:
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
Который затем позволяет нам определить 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)
Sledgehammer: Obj.magic
Один (простой, но грязный) способ избавиться от не хвост рекурсивной shift
функции заменить ее Obj.magic
: в самом деле, как уже упоминалось ранее, наш shift
s - это не что иное, как структурно определенные функции идентичности.
Это приводит нас к:
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)
Спасибо за этот ответ, объясняющих хорошо вопрос типизации. Это, однако, не очень удовлетворительно, так как цель 'add2' должна быть обратной рекурсивной. Это достигается за счет использования другой нерекурсивной функции возврата ... – lavi
@lavi См. Мое редактирование: я добавил 'shift3', который ведет себя так же, как' shift', за исключением того, что он определен в стиле продолжения прохождения так, что он рекурсивный , а также небольшой абзац «Obj.magic». Я предпочел бы придерживаться безопасной части языка и использовать CPS. – gallais
Да, технически 'shift3' является хвостом рекурсивным. Однако нет выигрыша, так как потребление стека одинаковое, с большим увеличением продолжительности. Поэтому я все еще немного озадачен. Пергапс хорошее решение заключается в том, чтобы использовать Obj.magic для кодирования инварианта разности ... Но тогда с GADT нет усиления. Раньше в Ocaml этот материал был закодирован фантомным типом, поэтому не было необходимости Obj.magic. Я бы надеялся, что GADT принесет мне более чистый код, но теперь я не уверен. – lavi