A nice true fact about concatenation is that if I know any two variables in the equation:
a ++ b = c
Then I know the third.
I would like to capture this idea in my own concat so I use a functional dependency.
{-# 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
Now I conjure heterogeneous list like so:
data HList ( as :: [ Type ] ) where
HEmpty :: HList '[]
HCons :: a -> HList as -> HList (a ': as)
But when I try to declare these as Concatable
I have an issue
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)
I don't satisfy my third functional dependency. Or rather the compiler believes we do not.
This is because the compiler believes that in our second instance it might be the case that bs ~ (a ': cs)
. And it could be the case if Concatable as (a ': cs) cs
.
How can I adjust my instances so that all three of the dependencies are satisfied?
bs cs -> as
, because we need non-local information aboutbs
andcs
to decide ifas
should be a cons or a nil. We need to find out how to represent this information; what context would we add to a type signature to guarantee it when it can't be directly deduced? – Orthopticbs
andcs
, and we want to exploit the fundep, i.e. we want to reconstructas
. To do it in a deterministic way, we expect to be able to commit to a single instance and follow that recipe. Concretely, assumebs = (Int ': bs2)
andcs = (Int ': cs2)
. Which instance do we choose? It is possible that suchInt
incs
comes frombs
(andas
is empty). It is also possible that is comes from (nonempty)as
instead, and thatInt
will appear again incs
later on. We need to dig deeper incs
to know and GHC won't do that. – Streetcar