Ограничение входных аргументов функцией

Допустим, я хочу определить функцию Фибоначчи как следующую функцию:

fibo : Int -> Int
fibo 1 = 1
fibo 2 = 2
fibo n = fibo (n-1) + fibo (n-2)

Эта функция, очевидно, не является полной, поскольку она не определена для целых чисел ниже 1, поэтому мне нужно как-то ограничить входной аргумент.

Я пробовал играть с определением нового типа данных MyInt. Что-то в духе:

-- bottom is the lower limit
data MyInt : (bottom: Int) -> (n: Int) -> Type
  where
    ...

fibo : MyInt 1 n -> Int
...

Однако я довольно быстро теряюсь.

Как я могу ограничить входной аргумент, например, моей функцией fibo, чтобы он был целочисленным значением 1 или выше?


person Michelrandahl    schedule 30.06.2016    source источник


Ответы (1)


На самом деле есть две причины, по которым Идрис не распознает функцию fibo как общую. Во-первых, как вы указали, он не определен для целых чисел меньше 1, а во-вторых, он вызывает себя рекурсивно. Хотя Idris способен распознавать все рекурсивные функции, обычно он может это делать только тогда, когда можно показать, что аргумент рекурсивного вызова «меньше» (т.е. ближе к базовому случаю*), чем исходный аргумент (например, , если функция получает список в качестве аргумента, она может вызвать себя с хвостом списка, не обязательно жертвуя целостностью, потому что хвост является подструктурой исходного списка и, следовательно, ближе к Nil). Проблема с такими выражениями, как (n-1) и (n-2), когда они имеют тип Int, заключается в том, что хотя они численно меньше n, они не структурно меньше, потому что Int не равно определено индуктивно и поэтому не имеет базовых случаев. Поэтому средство проверки целостности не может убедиться, что рекурсия всегда в конце концов достигает базового случая (даже если это может показаться нам очевидным), и поэтому оно не будет считать fibo полным.

Прежде всего, давайте решим проблему рекурсии. Вместо Int мы можем использовать индуктивно определенный тип данных, такой как Nat:

data Nat =
  Z | S Nat

(Натуральное число либо равно нулю, либо является преемником другого натурального числа.)

Это позволяет нам переписать fibo как:

fibo : Nat -> Int
fibo (S Z)     = 1
fibo (S (S Z)) = 2
fibo (S (S n)) = fibo (S n) + fibo n

(Обратите внимание, что в рекурсивном случае вместо явного вычисления (n-1) и (n-2) мы получаем их путем сопоставления с образцом аргумента, тем самым демонстрируя Идрису, что они структурно меньше.)

Однако это новое определение fibo все еще не является полностью полным, поскольку в нем отсутствует случай для Z (т. е. ноль). Если мы не хотим предусмотреть такой случай, то мы должны дать Идрису некоторые гарантии, что этого не допустят. Один из способов сделать это — потребовать доказательства того, что аргумент fibo больше или равен единице (или, что то же самое, единица меньше или равна аргументу):

fibo : (n : Nat) -> LTE 1 n -> Int
fibo Z LTEZero impossible
fibo Z (LTESucc _) impossible
fibo (S Z) _ = 1
fibo (S (S Z)) _ = 2
fibo (S (S (S n))) _ = fibo (S (S n)) (LTESucc LTEZero) + fibo (S n) (LTESucc LTEZero)

LTE 1 n — это тип, значения которого являются доказательством того, что 1 ≤ n (в пределах натуральных чисел). LTEZero представляет аксиому, согласно которой ноль ≤ любого натурального числа, а LTESucc представляет правило, что если n ≤ m, то (последователь n) ≤ (последователь m ). Ключевое слово impossible указывает, что данный случай не может произойти. В приведенном выше определении невозможно, чтобы первый аргумент fibo был равен нулю, потому что нет способа доказать, что 1 ≤ 0. Для любого другого натурального числа n мы можем доказать, что 1 ≤ n используя (LTESucc LTEZero).

Теперь, наконец, fibo является полным, но довольно громоздко предоставлять ему явное доказательство того, что его аргумент больше или равен 1. К счастью, мы можем пометить аргумент доказательства как автоматически неявный:

fibo : (n : Nat) -> {auto p : LTE 1 n} -> Int
fibo Z {p = LTEZero} impossible
fibo Z {p = (LTESucc _)} impossible
fibo (S Z) = 1
fibo (S (S Z)) = 2
fibo (S (S (S n))) = fibo (S (S n)) + fibo (S n)

Идрис теперь автоматически найдет доказательство того, что 1 ≤ n, где это возможно, в противном случае нам все равно придется предоставить его самим.


* Вполне могут быть некоторые тонкости, связанные с кодовыми данными, которые я здесь не замечаю, но это общий принцип.

person j11c    schedule 02.07.2016