Ich habe kürzlich herausgefunden, dass Typlöcher in Kombination mit Mustervergleich auf Proofs eine ziemlich schöne Agda-ähnliche Erfahrung in Haskell bieten. Beispielsweise:
{-# LANGUAGE
DataKinds, PolyKinds, TypeFamilies,
UndecidableInstances, GADTs, TypeOperators #-}
data (==) :: k -> k -> * where
Refl :: x == x
sym :: a == b -> b == a
sym Refl = Refl
data Nat = Zero | Succ Nat
data SNat :: Nat -> * where
SZero :: SNat Zero
SSucc :: SNat n -> SNat (Succ n)
type family a + b where
Zero + b = b
Succ a + b = Succ (a + b)
addAssoc :: SNat a -> SNat b -> SNat c -> (a + (b + c)) == ((a + b) + c)
addAssoc SZero b c = Refl
addAssoc (SSucc a) b c = case addAssoc a b c of Refl -> Refl
addComm :: SNat a -> SNat b -> (a + b) == (b + a)
addComm SZero SZero = Refl
addComm (SSucc a) SZero = case addComm a SZero of Refl -> Refl
addComm SZero (SSucc b) = case addComm SZero b of Refl -> Refl
addComm sa@(SSucc a) sb@(SSucc b) =
case addComm a sb of
Refl -> case addComm b sa of
Refl -> case addComm a b of
Refl -> Refl
Das wirklich Schöne ist, dass ich die rechten Seiten der Refl -> exp
Konstruktionen durch ein Typloch ersetzen kann und meine Lochzieltypen mit dem Beweis aktualisiert werden, so ziemlich wie mit dem rewrite
Formular in Agda.
Manchmal kann das Loch jedoch einfach nicht aktualisiert werden:
(+.) :: SNat a -> SNat b -> SNat (a + b)
SZero +. b = b
SSucc a +. b = SSucc (a +. b)
infixl 5 +.
type family a * b where
Zero * b = Zero
Succ a * b = b + (a * b)
(*.) :: SNat a -> SNat b -> SNat (a * b)
SZero *. b = SZero
SSucc a *. b = b +. (a *. b)
infixl 6 *.
mulDistL :: SNat a -> SNat b -> SNat c -> (a * (b + c)) == ((a * b) + (a * c))
mulDistL SZero b c = Refl
mulDistL (SSucc a) b c =
case sym $ addAssoc b (a *. b) (c +. a *. c) of
-- At this point the target type is
-- ((b + c) + (n * (b + c))) == (b + ((n * b) + (c + (n * c))))
-- The next step would be to update the RHS of the equivalence:
Refl -> case addAssoc (a *. b) c (a *. c) of
Refl -> _ -- but the type of this hole remains unchanged...
Auch wenn die Zieltypen nicht unbedingt im Proof ausgerichtet sind, wird beim Einfügen des Ganzen von Agda immer noch einwandfrei überprüft:
mulDistL' :: SNat a -> SNat b -> SNat c -> (a * (b + c)) == ((a * b) + (a * c))
mulDistL' SZero b c = Refl
mulDistL' (SSucc a) b c = case
(sym $ addAssoc b (a *. b) (c +. a *. c),
addAssoc (a *. b) c (a *. c),
addComm (a *. b) c,
sym $ addAssoc c (a *. b) (a *. c),
addAssoc b c (a *. b +. a *. c),
mulDistL' a b c
) of (Refl, Refl, Refl, Refl, Refl, Refl) -> Refl
Haben Sie eine Idee, warum dies passiert (oder wie ich das Umschreiben von Proofs auf robuste Weise durchführen könnte)?
haskell
dependent-type
András Kovács
quelle
quelle
sym
Anrufe weglassenmulDistL'
und Ihr Code würde immer noch prüfen.Antworten:
Wenn Sie alle möglichen derartigen Werte generieren möchten, können Sie dazu eine Funktion schreiben, entweder mit bereitgestellten oder angegebenen Grenzen.
Es mag durchaus möglich sein, Kirchennummern auf Typebene oder ähnliche zu verwenden, um die Erstellung dieser zu erzwingen, aber es ist fast definitiv zu viel Arbeit für das, was Sie wahrscheinlich wollen / brauchen.
Dies ist möglicherweise nicht das, was Sie möchten (dh "Außer die Verwendung von nur (x, y), da z = 5 - x - y"), aber es ist sinnvoller, als zu versuchen, eine erzwungene Einschränkung der Typebene zuzulassen, um die Gültigkeit zuzulassen Werte.
quelle
Dies geschieht, weil die Werte zur Laufzeit ermittelt werden. Es kann eine Transformation von Werten bewirken, abhängig davon, was zur Laufzeit eingegeben wird. Daher wird davon ausgegangen, dass das Loch bereits aktualisiert wurde.
quelle