2013-07-28 6 views
6

Agda 2.3.2.1 kann nicht sehen, dass die folgende Funktion beendet:Abschlusswiderstandsprüftyps auf Liste verschmelzen

open import Data.Nat 
open import Data.List 
open import Relation.Nullary 

merge : List ℕ → List ℕ → List ℕ 
merge (x ∷ xs) (y ∷ ys) with x ≤? y 
... | yes p = x ∷ merge xs (y ∷ ys) 
... | _  = y ∷ merge (x ∷ xs) ys 
merge xs ys = xs ++ ys 

Agda Wiki sagt, dass es für die Beendigung Prüfung OK ist, wenn die Argumente auf rekursive Aufrufe lexikografisch verringern. Basierend darauf scheint diese Funktion auch bestanden zu haben. Also was fehlt mir hier? Auch, ist es vielleicht in früheren Versionen von Agda OK? Ich habe ähnlichen Code im Internet gesehen und niemand erwähnte Kündigungsprobleme dort.

Antwort

6

Ich kann Ihnen nicht den Grund geben, warum genau das passiert, aber ich kann Ihnen zeigen, wie man die Symptome kuriert. Bevor ich beginne: Dies ist eine known problem mit der Terminierungsprüfung. Wenn Sie sich in Haskell auskennen, können Sie sich die source ansehen.


Eine mögliche Lösung ist die Funktion in zwei aufgeteilt: erste für den Fall, dass das erste Argument kleiner und die zweite für die zweite bekommt:

mutual 
    merge : List ℕ → List ℕ → List ℕ 
    merge (x ∷ xs) (y ∷ ys) with x ≤? y 
    ... | yes _ = x ∷ merge xs (y ∷ ys) 
    ... | no _ = y ∷ merge′ x xs ys 
    merge xs ys = xs ++ ys 

    merge′ : ℕ → List ℕ → List ℕ → List ℕ 
    merge′ x xs (y ∷ ys) with x ≤? y 
    ... | yes _ = x ∷ merge xs (y ∷ ys) 
    ... | no _ = y ∷ merge′ x xs ys 
    merge′ x xs [] = x ∷ xs 

Also, die erste Funktion Koteletts nach unten xs und sobald wir ys hacken müssen, wechseln wir zur zweiten Funktion und umgekehrt.


Weitere (vielleicht überraschend) Option, die auch in der Frage Bericht erwähnt wird, ist das Ergebnis der Rekursion über with vorstellen:

merge : List ℕ → List ℕ → List ℕ 
merge (x ∷ xs) (y ∷ ys) with x ≤? y | merge xs (y ∷ ys) | merge (x ∷ xs) ys 
... | yes _ | r | _ = x ∷ r 
... | no _ | _ | r = y ∷ r 
merge xs ys = xs ++ ys 

Und schließlich können wir die führen Rekursion auf Vec toren und dann konvertieren zurück zu List:

open import Data.Vec as V 
    using (Vec; []; _∷_) 

merge : List ℕ → List ℕ → List ℕ 
merge xs ys = V.toList (go (V.fromList xs) (V.fromList ys)) 
    where 
    go : ∀ {n m} → Vec ℕ n → Vec ℕ m → Vec ℕ (n + m) 
    go {suc n} {suc m} (x ∷ xs) (y ∷ ys) with x ≤? y 
    ... | yes _     = x ∷ go xs (y ∷ ys) 
    ... | no _ rewrite lem n m = y ∷ go (x ∷ xs) ys 
    go xs ys = xs V.++ ys 

jedoch hier brauchen wir eine einfache Lemma:

open import Relation.Binary.PropositionalEquality 

lem : ∀ n m → n + suc m ≡ suc (n + m) 
lem zero m     = refl 
lem (suc n) m rewrite lem n m = refl 

Wir auch go Rückkehr List direkt haben könnte und vermeiden das Lemma zusammen:

merge : List ℕ → List ℕ → List ℕ 
merge xs ys = go (V.fromList xs) (V.fromList ys) 
    where 
    go : ∀ {n m} → Vec ℕ n → Vec ℕ m → List ℕ 
    go (x ∷ xs) (y ∷ ys) with x ≤? y 
    ... | yes _ = x ∷ go xs (y ∷ ys) 
    ... | no _ = y ∷ go (x ∷ xs) ys 
    go xs ys = V.toList xs ++ V.toList ys 

Der erste Trick (d Teilen Sie die Funktion in einige gegenseitig rekursive) ist eigentlich ziemlich gut zu erinnern. Da die Abschlussprüfung innerhalb der Definitionen von anderen Funktionen verwenden Sie sieht nicht aus, es lehnt viel völlig in Ordnung Programme betrachten:

data Rose {a} (A : Set a) : Set a where 
    [] :      Rose A 
    node : A → List (Rose A) → Rose A 

Und jetzt möchten wir umsetzen möchten mapRose:

mapRose : ∀ {a b} {A : Set a} {B : Set b} → 
      (A → B) → Rose A → Rose B 
mapRose f []   = [] 
mapRose f (node t ts) = node (f t) (map (mapRose f) ts) 

Der Terminierungsprüfer sieht jedoch nicht innerhalb der map, um zu sehen, ob es nichts mit den Elementen funky macht und nur diese Definition zurückweist.Wir müssen die Definition von map Inline- und ein Paar einander rekursiven Funktionen schreiben:

mutual 
    mapRose : ∀ {a b} {A : Set a} {B : Set b} → 
      (A → B) → Rose A → Rose B 
    mapRose f []   = [] 
    mapRose f (node t ts) = node (f t) (mapRose′ f ts) 

    mapRose′ : ∀ {a b} {A : Set a} {B : Set b} → 
      (A → B) → List (Rose A) → List (Rose B) 
    mapRose′ f []  = [] 
    mapRose′ f (t ∷ ts) = mapRose f t ∷ mapRose′ f ts 

Normalerweise Sie die meisten der Unordnung in einem where Erklärung verstecken kann:

mapRose : ∀ {a b} {A : Set a} {B : Set b} → 
      (A → B) → Rose A → Rose B 
mapRose {A = A} {B = B} f = go 
    where 
    go  :  Rose A →  Rose B 
    go-list : List (Rose A) → List (Rose B) 

    go []   = [] 
    go (node t ts) = node (f t) (go-list ts) 

    go-list []  = [] 
    go-list (t ∷ ts) = go t ∷ go-list ts 

Hinweis: Deklarieren von Unterschriften beider Funktionen, bevor sie definiert sind, können anstelle von mutual in neueren Versionen von Agda verwendet werden.


Update: Die Entwickler-Version von Agda bekam ein Update auf die Beendigung checker, werde ich die Meldung verpflichten lassen und Release Notes sprechen für sich:

  • Eine Revision des Aufrufgraphen Abschluss das kann mit beliebiger Abbruchtiefe umgehen. Dieser Algorithmus sitzt seit einiger Zeit in MiniAgda, wartet auf seinen großen Tag. Es ist jetzt hier! Option - Die Abschlusstiefe kann jetzt zurückgezogen werden.

Und aus der Release Notes:

  • Termination Überprüfung der Funktionen, die durch definiert 'durch' wurde verbessert.

    Fälle, die zuvor --termination eingehende erforderlich (jetzt veraltet!) die Beendigung checker (aufgrund der Verwendung von ‚mit‘) nicht länger
    müssen die Fahne zu übergeben. Zum Beispiel

    merge : List A → List A → List A 
    merge [] ys = ys 
    merge xs [] = xs 
    merge (x ∷ xs) (y ∷ ys) with x ≤ y 
    merge (x ∷ xs) (y ∷ ys) | false = y ∷ merge (x ∷ xs) ys 
    merge (x ∷ xs) (y ∷ ys) | true = x ∷ merge xs (y ∷ ys) 
    

    Dies scheiterte bisher an Abschlusswiderstandsprüftyps, da die ‚mit‘ erweitert auf eine Hilfsfunktion merge-aux:

    merge-aux x y xs ys false = y ∷ merge (x ∷ xs) ys 
    merge-aux x y xs ys true = x ∷ merge xs (y ∷ ys) 
    

    dieser Funktion wird ein Anruf, in dem fusionieren macht die Größe Eines der Argumente steigt. Um dies zu bestehen, inliniert der Terminierungsprüfer nun die Definition von merge-aux vor der Überprüfung, also effektiv Kündigung das ursprüngliche Quellprogramm zu überprüfen.

    Als Ergebnis dieser Umwandlung macht 'doing' mit einer Variablen Nr. länger die Beendigung. Zum Beispiel, tut dies nicht Abschlussprüfung:

    bad : Nat → Nat 
    bad n with n 
    ... | zero = zero 
    ... | suc m = bad m 
    

Und in der Tat, Ihre ursprüngliche Funktion übergibt nun die Abschlussprüfung!

+0

Danke, das ist nur die Antwort, die ich suchte. –