Recientemente descubrí que los agujeros de tipo combinados con la coincidencia de patrones en las pruebas brindan una experiencia bastante agradable al estilo de Agda en Haskell. Por ejemplo:
{-# 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
Lo realmente bueno es que puedo reemplazar los lados derechos de las Refl -> exp
construcciones con un tipo de agujero, y mis tipos de objetivo de agujero se actualizan con la prueba, casi como con el rewrite
formulario en Agda.
Sin embargo, a veces el agujero simplemente no se actualiza:
(+.) :: 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...
Además, aunque los tipos de destino no se alinean necesariamente dentro de la prueba, si pego todo el contenido de Agda, todavía se verifica 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
¿Tiene alguna idea de por qué sucede esto (o cómo podría hacer una reescritura de pruebas de una manera sólida)?
haskell
dependent-type
András Kovács
fuente
fuente
sym
llamadasmulDistL'
y su código aún se verificará.Respuestas:
Si desea generar todos los valores posibles, puede escribir una función para hacerlo, ya sea con límites proporcionados o especificados.
Es muy posible que se puedan utilizar números de iglesia de nivel de tipo o algo así para hacer cumplir la creación de estos, pero es casi seguro que es demasiado trabajo para lo que probablemente desee o necesite.
Es posible que esto no sea lo que desea (es decir, "Excepto usar solo (x, y) ya que z = 5 - x - y") pero tiene más sentido que intentar tener algún tipo de restricción impuesta en el nivel de tipo para permitir valores.
fuente
Sucede porque los valores se determinan en tiempo de ejecución. Puede provocar una transformación de valores dependiendo de lo que se ingrese en el tiempo de ejecución, por lo que asume que el agujero ya está actualizado.
fuente