J'ai récemment découvert que les trous de type combinés à la correspondance de motifs sur les épreuves offrent une expérience assez agréable de type Agda dans Haskell. Par exemple:
{-# 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
Ce qui est vraiment sympa, c'est que je peux remplacer les côtés droits des Refl -> exp
constructions par un type de trou, et mes types de cible de trou sont mis à jour avec la preuve, à peu près comme avec le rewrite
formulaire dans Agda.
Cependant, parfois, le trou ne parvient pas à se mettre à jour:
(+.) :: 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...
De plus, même si les types de cibles ne s'alignent pas nécessairement à l'intérieur de la preuve, si je colle le tout depuis Agda, cela vérifie toujours bien:
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
Avez-vous une idée de pourquoi cela se produit (ou comment je pourrais réécrire des preuves de manière robuste)?
la source
sym
appelsmulDistL'
et votre code sera toujours vérifié.Réponses:
Si vous souhaitez générer toutes ces valeurs possibles, vous pouvez écrire une fonction pour le faire, avec des limites fournies ou spécifiées.
Il peut très bien être possible d'utiliser des numéros d'église au niveau du type ou d'autres afin d'imposer la création de ceux-ci, mais c'est presque certainement trop de travail pour ce que vous voulez / avez probablement besoin.
Ce n'est peut-être pas ce que vous voulez (c'est-à-dire "Sauf d'utiliser juste (x, y) puisque z = 5 - x - y") mais cela a plus de sens que d'essayer d'avoir une sorte de restriction appliquée au niveau du type pour autoriser des valeurs.
la source
Cela se produit parce que les valeurs sont déterminées au moment de l'exécution. Il peut entraîner une transformation des valeurs en fonction de ce qui est entré au moment de l'exécution, par conséquent, il suppose que le trou est déjà mis à jour.
la source