El sistema de tipos y la programación lógica de Haskell: cómo portar programas de Prolog a nivel de tipo
type-systems logic-programming (3)
Como @ stephen tetley
ya señaló cuando GHC intenta hacer coincidir la declaración de la instancia, considera que solo la cabeza de la instancia (las cosas después de =>) ignora por completo el contexto de la instancia (cosas antes de =>), una vez que se encuentra la instancia inequívoca, intenta hacer coincidir el contexto de la instancia . Su primer ejemplo problemático claramente tiene duplicación en el encabezado de la instancia, pero se puede arreglar fácilmente reemplazando dos instancias en conflicto con una instancia más general:
instance (Greaterthan a b r) => Greaterthan (Succ a) (Succ b) r
El segundo ejemplo, sin embargo, es mucho más difícil. Sospecho que para que funcione en Haskell necesitamos una función de nivel de tipo que pueda producir dos resultados diferentes dependiendo de si una instancia específica está definida o no para los argumentos de un tipo en particular (es decir, si hay una instancia Child Name1 Name2
- recursively do algo con Name2
contrario devuelve BFalse
). No estoy seguro de si es posible codificar esto con tipos de GHC (sospecho que no lo es).
Sin embargo, puedo proponer una "solución" que funciona para un tipo de entrada ligeramente diferente: en lugar de implicar la ausencia de una parent->child
(cuando no se define una instancia para tal par), podríamos codificar explícitamente todas las relaciones existentes utilizando listas de nivel de tipo . Entonces podemos definir la función de nivel de tipo Descend
, aunque tiene que depender de la extensión OverlappingInstances muy temida:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, FlexibleContexts, TypeOperators,
UndecidableInstances, OverlappingInstances #-}
data Anne
data Bridget
data Caroline
data Donna
data Emily
data Fred
data George
-- Type-level list
data Nil
infixr 5 :::
data x ::: xs
-- `bs` are children of `a`
class Children a bs | a -> bs
instance Children Anne (Bridget ::: Nil)
instance Children Bridget (Caroline ::: Donna ::: Nil)
instance Children Caroline (Emily ::: Nil)
-- Note that we have to specify children list for everyone
-- (`Nil` instead of missing instance declaration)
instance Children Donna Nil
instance Children Emily Nil
instance Children Fred (George ::: Nil)
instance Children George Nil
-- `or` operation for type-level booleans
class OR a b bool | a b -> bool
instance OR BTrue b BTrue
instance OR BFalse BTrue BTrue
instance OR BFalse BFalse BFalse
-- Is `a` a descendant of `b`?
class Descend a b bool | a b -> bool
instance (Children b cs, PathExists cs a r) => Descend a b r
-- auxiliary function which checks if there is a transitive relation
-- to `to` using all possible paths passing `children`
class PathExists children to bool | children to -> bool
instance PathExists Nil a BFalse
instance PathExists (c ::: cs) c BTrue
instance (PathExists cs a r1, Children c cs1, PathExists cs1 a r2, OR r1 r2 r)
=> PathExists (c ::: cs) a r
-- Some tests
instance Show BTrue where
show _ = "BTrue"
instance Show BFalse where
show _ = "BFalse"
t1 :: Descend Donna Anne r => r
t1 = undefined -- outputs `BTrue`
t2 :: Descend Fred Anne r => r
t2 = undefined -- outputs `BFalse`
OverlappingInstances
este caso, es necesario que las OverlappingInstances
se PathExists
ya que la segunda y la tercera instancia de PathExists
pueden coincidir con el caso cuando los children
no están en la lista vacía, pero GHC puede determinar una más específica en nuestro caso, dependiendo de si el encabezado de la lista es igual to
argumento (y si lo es. significa que hemos encontrado el camino es decir, descendiente).
Estoy tratando de entender la relación entre un lenguaje de programación lógico (Prolog en mi caso) y el sistema de tipos de Haskell.
Sé que ambos usan la unificación y las variables para encontrar valores (o tipos, en el sistema de tipos de Haskell) dependiendo de las relaciones. Como ejercicio para comprender mejor las similitudes y diferencias entre ellos, traté de reescribir algunos programas de prólogo simples en el nivel de tipo de Haskell, pero tengo problemas con algunas partes.
Primero, reescribí este sencillo programa de prólogo:
numeral(0).
numeral(succ(X)) :- numeral(X).
add(0,Y,Y).
add(succ(X),Y,succ(Z)) :- add(X,Y,Z).
como:
class Numeral a where
numeral :: a
numeral = u
data Zero
data Succ a
instance Numeral Zero
instance (Numeral a) => Numeral (Succ a)
class (Numeral a, Numeral b, Numeral c) => Add a b c | b c -> a where
add :: b -> c -> a
add = u
instance (Numeral a) => Add a Zero a
instance (Add x y z) => Add (Succ x) (Succ y) z
funciona bien, pero no pude extenderlo con este Prolog:
greater_than(succ(_),0).
greater_than(succ(X),succ(Y)) :- greater_than(X,Y).
Lo que intenté fue esto:
class Boolean a
data BTrue
data BFalse
instance Boolean BTrue
instance Boolean BFalse
class (Numeral a, Numeral b, Boolean r) => Greaterthan a b r | a b -> r where
greaterthan :: a -> b -> r
greaterthan = u
instance Greaterthan Zero Zero BFalse
instance (Numeral a) => Greaterthan (Succ a) Zero BTrue
instance (Numeral a) => Greaterthan Zero (Succ a) BFalse
instance (Greaterthan a b BTrue) => Greaterthan (Succ a) (Succ b) BTrue
instance (Greaterthan a b BFalse) => Greaterthan (Succ a) (Succ b) BFalse
El problema con este código es que las dos últimas instancias están causando un conflicto fundep. Puedo entender por qué, pero me parece que no debería ser un problema ya que sus partes de guardia (o como se llame, me refiero a la (Greaterthan abc) =>
) son diferentes, por lo que a
s y b
s en las últimas dos declaraciones de cumplimiento son en realidad valores diferentes y no hay conflictos.
Otro programa que intenté reescribir fue este:
child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend(X,Y) :- child(X,Y).
descend(X,Y) :- child(X,Z),
descend(Z,Y).
(por cierto, los ejemplos son del libro Learn Prolog Now )
data Anne
data Bridget
data Caroline
data Donna
data Emily
class Child a b | a -> b where
child :: a -> b
child = u
instance Child Anne Bridget
instance Child Bridget Caroline
instance Child Caroline Donna
instance Child Donna Emily
class Descend a b | b -> a where
descend :: b -> a
descend = u
instance (Child a b) => Descend a b
instance (Child a c, Descend c b) => Descend a b
Y me aparece el error "duplicar instancias" en la última línea. Creo que es un problema similar, incluso si tengo partes de guardia diferentes, recibo errores porque las partes del cuerpo (me refiero a las partes Descend ab
) son las mismas.
Así que estoy buscando formas de trasladar los programas Prolog al nivel de tipo de Haskell, si es posible. Cualquier ayuda será apreciada.
EDITAR:
La solución de Ed''ka funciona pero de una manera completamente diferente. Todavía estoy tratando de entender cuándo podemos ejecutar un programa Prolog en el sistema de tipos, cuándo / por qué necesitamos escribir un algoritmo diferente para que funcione (como en la solución de Ed''ka), y cuándo / por qué no hay manera de Implementar un programa en el sistema de tipos de Haskell.
Tal vez pueda encontrar algunos consejos sobre esto después de leer "Diversión con dependencias funcionales".
En cuanto al ejemplo de GreaterThan
, no veo cómo introducir esos Boolean
es un paso fiel al código Prolog original. Parece que está intentando codificar un sentido de decidibilidad en su versión de Haskell que no está presente en la versión Prolog.
Así que, en general, puedes hacer
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Numeral a where
data Zero
data Succ a
instance Numeral Zero
instance (Numeral a) => Numeral (Succ a)
class (Numeral a, Numeral b) => Greaterthan a b where
instance (Numeral a) => Greaterthan Zero (Succ a)
instance (Greaterthan a b) => Greaterthan (Succ a) (Succ b)
En realidad, con los tipos de datos , puedes escribirlo mejor (pero no puedo intentarlo ahora, ya que solo tengo ghc 7.2 instalado aquí):
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
data Numeral = Zero | Succ Numeral
class Greaterthan (a :: Numeral) (b :: Numeral) where
instance Greaterthan Zero (Succ a)
instance (Greaterthan a b) => Greaterthan (Succ a) (Succ b)
Para la solución Ed''ka usted podría usar:
import Data.HList.TypeCastGeneric2
instance TypeCast nil Nil => Children a nil
en lugar de una instancia para cada persona que no tiene hijos.