Um bom fato verdadeiro sobre concatenação é que, se eu conhecer duas variáveis na equação:
a ++ b = c
Então eu conheço o terceiro.
Gostaria de capturar essa ideia em minha própria concat, para usar uma dependência 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
Agora, conjuro uma lista heterogênea da seguinte forma:
data HList ( as :: [ Type ] ) where
HEmpty :: HList '[]
HCons :: a -> HList as -> HList (a ': as)
Mas quando tento declarar isso, Concatable
tenho um 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)
Não satisfiz minha terceira dependência funcional. Ou melhor, o compilador acredita que não. Isso ocorre porque o compilador acredita que, em nossa segunda instância, pode ser o caso bs ~ (a ': cs)
. E poderia ser o caso se Concatable as (a ': cs) cs
.
Como posso ajustar minhas instâncias para que todas as três dependências sejam atendidas?
haskell
typeclass
functional-dependencies
type-level-computation
Sriotchilism O'Zaic
fonte
fonte
bs cs -> as
, porque precisamos de informações não locais sobrebs
ecs
decidir seas
deve ser um contra ou um nada. Precisamos descobrir como representar essa informação; que contexto adicionaríamos a uma assinatura de tipo para garantir quando não puder ser deduzida diretamente?bs
ecs
, e queremos explorar o fundep, ou seja, queremos reconstruiras
. Para fazer isso de maneira determinística, esperamos poder comprometer-se com uma única instância e seguir essa receita. Concretamente, assumabs = (Int ': bs2)
ecs = (Int ': cs2)
. Qual instância escolhemos? É possível que taisInt
emcs
vem debs
(eas
está vazio). Também é possível que venha de (não vazio)as
e queInt
apareça novamentecs
mais tarde. Precisamos nos aprofundarcs
para saber e GHC não fará isso.Respostas:
Então, como sugerem os comentários, o GHC não vai descobrir por si só, mas podemos ajudá-lo com um pouco de programação em nível de tipo. Vamos apresentar alguns
TypeFamilies
. Todas essas funções são traduções bastante diretas da manipulação de lista levantadas para o nível de tipo:Com essas ferramentas à nossa disposição, podemos chegar ao objetivo da hora, mas primeiro vamos definir uma função com as propriedades desejadas:
cs
a partiras
ebs
as
a partirbs
ecs
bs
a partiras
ecs
Voila:
Vamos testá-lo:
E, finalmente, o objetivo final:
fonte