Resolución de tipo de agujero errático

105

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 -> expconstrucciones con un tipo de agujero, y mis tipos de objetivo de agujero se actualizan con la prueba, casi como con el rewriteformulario 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)?

András Kovács
fuente
8
¿No esperas mucho? La coincidencia de patrones en una prueba de igualdad establece una igualdad (bidireccional). No está del todo claro dónde y en qué dirección desea que se aplique al tipo de destino. Por ejemplo, puede omitir las symllamadas mulDistL'y su código aún se verificará.
kosmikus
1
Es muy posible que esté esperando demasiado. Sin embargo, en muchos casos funciona igual que en Agda, por lo que aún sería útil averiguar las regularidades del comportamiento. Sin embargo, no soy optimista, ya que el asunto probablemente esté profundamente relacionado con las entrañas del verificador de tipos.
András Kovács
1
Es un poco ortogonal a su pregunta, pero puede realizar estas demostraciones utilizando un conjunto de combinadores de razonamiento ecuacional a la Agda. Cf. esta prueba de concepto
gallais

Respuestas:

1

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.

Billykart
fuente
-3

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.

ajay
fuente
3
Por supuesto, los valores se determinan en tiempo de ejecución, pero no tiene nada que ver con la pregunta. La información necesaria ya está disponible en la comparación de patrones en un GADT.
int_index