Убедить Agda в том, что рекурсивная функция завершается - PullRequest
1 голос
/ 09 мая 2020

Мне трудно убедить Agda в том, что аргумент в рекурсивном вызове функции структурно меньше, чем входящий аргумент.

Я определил пары, списки пар (представляющие конечные функции как "наборы "пар ввода / вывода), и объединения таких списков следующим образом:

data _x_ {l : Level} (A B : Set l) : Set l where
  <_,_> : A -> B → A x B

data FinFun (A B : Set) : Set where
  nil : FinFun A B
  _::_ : A x B → FinFun A B → FinFun A B

_U_ : {A B : Set} -> FinFun A B -> FinFun A B -> FinFun A B
nil U f' = f'
(x :: xs) U f' = x :: (xs U f')

Я также определил« окрестности »и верхнюю грань двух таких окрестностей:

data UniNbh : Set where
  bot : UniNbh
  lam : FinFun UniNbh UniNbh -> UniNbh

_u_ : UniNbh -> UniNbh -> UniNbh
bot u bot = bot
bot u (lam f) = lam f
(lam f) u bot = lam f
(lam f) u (lam f') = lam (f U f')

Наконец , и, что наиболее важно для этого вопроса, я определил функцию, которая, учитывая список пар окрестностей, берет верхнюю грань всех первых компонентов пар в списке:

pre : FinFun UniNbh UniNbh -> UniNbh
pre nil = bot
pre (< x , y > :: f) = x u pre f

Взаимно рекурсивный Функция, которая вызывает у меня проблемы, по существу выглядит так:

f : UniNbh -> UniNbh -> UniNbh -> Result
-- Base cases here. When any argument is bot or lam nil, no
-- recursion is needed.
f (lam (a ∷ as)) (lam (b ∷ bs)) (lam (c ∷ cs)) =
  f (lam (a ∷ as)) (pre (b ∷ bs)) (lam (c ∷ cs))

Кажется очевидным, что либо pre f меньше lam f, либо один из базовых случаев завершит рекурсию, но Agda по понятным причинам не может посмотри это. Я пробовал довольно много разных идей, пытаясь решить эту проблему, но они не сработали. На данный момент я думаю, что единственный способ - использовать Induction.WellFounded из стандартной библиотеки, но я не могу понять, как это сделать.

Я безуспешно пытался показать, что следующий тип данных подходит основан:

data preSmaller : UniNbh -> UniNbh -> Set where
  pre-base : preSmaller (pre nil) (lam nil)
  pre-step : ∀ (x y f f') ->
             preSmaller (pre f) (lam f') ->
             preSmaller  (pre (< x , y > :: f')) (lam (< x , y > :: f'))

Я даже не уверен, что этот тип данных был бы полезен, даже если бы я смог доказать, что он хорошо обоснован.

При поиске информации о том, как Чтобы использовать Induction.WellFounded, я могу найти только очень простые примеры, показывающие, что <для натуральных чисел хорошо обоснован, и я не смог обобщить эти идеи на эту ситуацию. </p>

Извините за длинный пост. Любая помощь будет принята с благодарностью!

1 Ответ

2 голосов
/ 10 мая 2020

Я не могу увидеть все определения из-за некоторого юникода - многие введенные вами символы отображаются как квадраты. Базовая c идея WellFounded не является доказательством того, что некоторый тип данных становится меньше. Основная идея c состоит в том, что Agda может видеть, что Acc _<_ x, созданный функцией доступа, заключенной в Acc _<_ y, становится меньше.

В вашем случае кажется, что preSmaller - это такой _<_. Трудно судить, так ли это, потому что много текста отсутствует. Затем вам нужно будет создать функцию, которая может построить Acc preSmaller y для любых двух заданных x y : UniNbh.


В отредактированном вопросе все еще отсутствуют некоторые определения (например, что такое post nil . Но я понимаю, что происходит.

Ваше определение preSmaller похоже на следующее определение _<_ для Nat:

data _<_ : Nat -> Nat -> Set where
   z<  : {n : Nat} -> zero < (succ n)
   s<s : {m n : Nat} -> m < n -> (succ m) < (succ n)

Обратите внимание, что это отличается от стандартного определения, потому что и m, и n становятся больше. Это влияет на построение доказательства WellFounded -ности.

-- may just as well import, but let me be self-contained:
data Acc {A : Set} (_<_ : A -> A -> Set) (x : A) : Set where
   acc : ((y : A) -> y < x -> Acc _<_ y) -> Acc _<_ x

Well-founded : (A : Set) -> (R : A -> A -> Set) -> Set
Well-founded A _<_ = (x : A) -> Acc _<_ x

{-# BUILTIN EQUALITY _==_ #-} -- rewrite rule needs this, if I am not using
-- Unicode version of it from Prelude
<-Well-founded : Well-founded Nat _<_
<-Well-founded zero     = acc \_ ()
<-Well-founded (succ x) = acc aux where
   aux : (y : Nat) -> y < (succ x) -> Acc _<_ y
   aux zero     _                                 = <-Well-founded zero
   aux (succ y) (s<s y<x) with <-Well-founded x | is-eq? (succ y) x
   ...          | acc f | no  sy!=x               = f (succ y) (neq y<x sy!=x)
   ...          | wf-x  | yes sy==x rewrite sy==x = wf-x

Вспомогательные функции:

data False : Set where

false-elim : {A : Set} -> False -> A
false-elim ()

data Dec (A : Set) : Set where
   yes : A -> Dec A
   no  : (A -> False) -> Dec A

_==?_ : {A : Set} -> A -> A -> Set
_==?_ x y = Dec (x == y)

s== : {m n : Nat} -> (succ m) == (succ n) -> m == n
s== refl = refl

is-eq? : (m n : Nat) -> m ==? n
is-eq? zero     zero     = yes refl
is-eq? (succ m) zero     = no \()
is-eq? zero     (succ n) = no \()
is-eq? (succ m) (succ n) with is-eq? m n
...                        | no  f   = no \sm=sn -> f (s== sm=sn)
...                        | yes m=n = yes (cong succ m=n)

-- if m < n and m+1 /= n, then m+1 < n
neq : {m n : Nat} -> m < n -> ((succ m) == n -> False) -> (succ m) < n
neq {_}      {zero}          ()
neq {zero}   {succ zero}     z<        f = false-elim (f refl)
neq {zero}   {succ (succ n)} z<        f = s<s z<
neq {succ m} {succ n}        (s<s m<n) f = s<s (neq m<n \m=n -> f (cong succ m=n))

Важные вещи, которые нужно убрать:

Стандартное определение _<_ позволяет построить более простое доказательство WellFounded -ности, потому что можно уменьшать один из аргументов за раз. другое определение _<_ требует сокращения обоих, и это кажется проблемой. Тем не менее, с помощью вспомогательной функции neq можно построить рекурсию, в которой только один и тот же аргумент становится меньше.

Реш. способность _==_ для Nat позволяет мне построить такую ​​рекурсию. Agda может видеть, что рекурсивный вызов <-WellFounded предназначен для структурно меньшего x, поэтому он завершается. Затем результат этого используется по-разному в зависимости от результата проверки равенства. Ветвь, использующая neq, вычисляет необходимое Acc с учетом функции, которую <-WellFounded обнаружил для меньшего x: функция завершается, потому что Agda разрешила создание такой функции. Другая ветвь, где x == (succ y), использует значение как есть, потому что rewrite убеждает Agda в правильности его типа.


Обоснованность затем может использоваться для доказательства функции завершается путем создания экземпляра <-WellFounded:

_-|-_ : Bin -> Bin -> Bin
x -|- y with max-len x y
...   | n , (x<n , y<n) = Sigma.fst (a (<-Well-founded n) b (x , x<n) (y , y<n)) where
  a : {n : Nat} -> Acc _<_ n -> Bin -> S-Bin n -> S-Bin n -> S-Bin (succ n)
  a+O : {n : Nat} -> Acc _<_ n -> Bin -> S-Bin n -> S-Bin n -> S-Bin (succ (succ n))
  a+I : {n : Nat} -> Acc _<_ n -> Bin -> S-Bin n -> S-Bin n -> S-Bin (succ (succ n))

  a+O f c m n with a f c m n
  ...                | r , r<n = r O , s<s r<n

  a+I f c m n with a f c m n
  ...                | r , r<n = r I , s<s r<n

  a {zero} _ _ (_ , ())
  a {succ sz} (acc f) cc mm nn with cc | mm | nn
  ... | b | m O , s<s m< | n O , s<s n< = a+O (f sz n<n1) b (m , m<) (n , n<)
  ... | b | m O , s<s m< | n I , s<s n< = a+I (f sz n<n1) b (m , m<) (n , n<)
   ....-- not including the whole thing here - it is too long.

Я не включаю всю конструкцию сложения двух двоичных чисел (также неэффективную - просто упражнение на доказательство обоснованности). Здесь важно отметить, как запускается рекурсия и как она повторно используется для создания новых экземпляров Acc для сопоставления типов - здесь S-Bin представляет двоичное число длиной не более n, а Agda убежден, что Acc _<_ n становится меньше, хотя не может доказать, что S-Bin n становится меньше.

...