Un buen hecho verdadero sobre la concatenación es que si conozco dos variables en la ecuación:
a ++ b = c
Entonces sé el tercero.
Me gustaría capturar esta idea en mi propia concat, así que uso una dependencia funcional.
{-# Language DataKinds, GADTs, FlexibleContexts, FlexibleInstances, FunctionalDependencies, KindSignatures, PolyKinds, TypeOperators, UndecidableInstances #-}
import Data.Kind (Type)
class Concatable
(m :: k -> Type)
(as :: k)
(bs :: k)
(cs :: k)
| as bs -> cs
, as cs -> bs
, bs cs -> as
where
concat' :: m as -> m bs -> m cs
Ahora conjuro una lista heterogénea así:
data HList ( as :: [ Type ] ) where
HEmpty :: HList '[]
HCons :: a -> HList as -> HList (a ': as)
Pero cuando trato de declarar esto ya Concatable
que tengo un problema
instance Concatable HList '[] bs bs where
concat' HEmpty bs = bs
instance
( Concatable HList as bs cs
)
=> Concatable HList (a ': as) bs (a ': cs)
where
concat' (HCons head tail) bs = HCons head (concat' tail bs)
No satisfago mi tercera dependencia funcional. O más bien, el compilador cree que nosotros no. Esto se debe a que el compilador cree que en nuestra segunda instancia podría ser el caso bs ~ (a ': cs)
. Y podría ser el caso si Concatable as (a ': cs) cs
.
¿Cómo puedo ajustar mis instancias para que se satisfagan las tres dependencias?
haskell
typeclass
functional-dependencies
type-level-computation
Sriotchilism O'Zaic
fuente
fuente
bs cs -> as
, porque necesitamos información no local sobrebs
ycs
decidir sias
debería ser una desventaja o no. Necesitamos descubrir cómo representar esta información; ¿Qué contexto agregaríamos a una firma de tipo para garantizarla cuando no se puede deducir directamente?bs
ycs
queremos explotar el fundep, es decir, queremos reconstruiras
. Para hacerlo de una manera determinista, esperamos poder comprometernos con una sola instancia y seguir esa receta. Concretamente, asumirbs = (Int ': bs2)
ycs = (Int ': cs2)
. ¿Qué instancia elegimos? Es posible que talInt
encs
proviene debs
(yas
está vacía). También es posible que provenga de (no vacío)as
y queInt
vuelva a aparecer máscs
adelante. Necesitamos profundizarcs
para saber y GHC no lo hará.Respuestas:
Entonces, como sugieren los comentarios, GHC no lo resolverá por sí solo, pero podemos ayudarlo con un poco de programación a nivel de tipo. Vamos a presentar algunos
TypeFamilies
. Todas estas funciones son traducciones bastante sencillas de manipulación de listas elevadas al nivel de tipo:Con estas herramientas a nuestra disposición, podemos llegar al objetivo por hora, pero primero definamos una función con las propiedades deseadas:
cs
a partir deas
ybs
as
a partir debs
ycs
bs
a partir deas
ycs
Voila
Probémoslo:
Y finalmente el objetivo final:
fuente