Como provar a igualdade de tipos indutivamente sem classes?

8

Estou tentando provar a associatividade das listas em nível de tipo de maneira que me permita converter entre tipos equivalentes sem contornar restrições.

Assumindo a definição padrão de concatenação:

type family (++) (xs :: [k]) (ys :: [k]) :: [k] where
  '[] ++ ys = ys
  (x ': xs) ++ ys = x ': (xs ++ ys)

Suponha que eu receba uma função:

given :: forall k (a :: [k]) (b :: [k]) (c :: [k]). Proxy ((a ++ b) ++ c)
given = Proxy  -- Proxy is just an example

e eu gostaria de chamar essa função e usar associatividade:

my :: forall k (a :: [k]) (b :: [k]) (c :: [k]). Proxy (a ++ (b ++ c))
my = given @k @a @b @c  -- Couldn't match type ‘(a ++ b) ++ c’ with ‘a ++ (b ++ c)’

Essa igualdade de tipos não é trivial, portanto, não é uma surpresa que o compilador não entenda, mas eu posso provar! Infelizmente, não sei como convencer o compilador que posso.

Meu primeiro pensamento natural é fazer algo como:

proof :: forall k (a :: [k]) (b :: [k]) (c :: [k]). (a ++ (b ++ c)) :~: ((a ++ b) ++ c)
proof = _

e depois mude minha função para:

my :: forall k (a :: [k]) (b :: [k]) (c :: [k]). Proxy (a ++ (b ++ c))
my = case proof @k @a @b @c of Refl -> given @k @a @b @c

Mas ainda tenho que definir proofe, para isso, preciso executar a indução em seus argumentos de tipo. A única maneira de fazer a indução de tipos no Haskell que eu sei é definir uma classe de tipo, mas então terei que adicionar a restrição correspondente ao tipo de myqual eu não quero fazer - o fato de chamar givene coagir o resultado é um "detalhe de implementação".

Existe alguma maneira de provar esse tipo de igualdade de tipo em Haskell sem recorrer a postulados inseguros?

Kirelagin
fonte
1
Este seria um caso de uso para tipos dependentes, mas Haskell não os possui. Precisamos recorrer a singletons (para que possamos corresponder a padrões e recursões), possivelmente por meio de classes de tipos. Eu não acho que você possa escrever um termo do tipo não inferior (a++(b++c)) :~: ((a++b)++c)sem mais argumentos singleton ou restrições de classe de tipo.
chi

Respostas:

6

Não, você não pode provar isso sem uma restrição de classe, porque não é verdade. Em particular, aqui está um contra-exemplo:

Any ++ ([] ++ []) -- reduces to Any ++ []
(Any ++ []) ++ [] -- does not reduce

Para descartar a existência (estúpida) de Any, você deve usar uma classe de tipo que não possui uma Anyinstância; nenhuma outra escolha.

Daniel Wagner
fonte
Ah, certo, eu continuo esquecendo Any:(. Eu esperava que as anotações gentis na minha definição de ++garantissem que isso fosse verdade, mas Anyclaramente quebra isso.
kirelagin
1
Não é Anybasicamente justo, undefinedmas no nível de tipo? Como é moralmente correto fingir que o último não existe, por que não podemos fazer o mesmo pelo primeiro?
Joseph Sible-Reinstate Monica
4
@ JosephSible-ReinstateMonica Certamente, podemos fingir tudo o que você gosta. E a maneira como você converte sua pretensão de nível de tipo para o nível de computação é usar unsafeCoerce. Mas a pergunta solicita explicitamente evitar unsafeCoercee, portanto, não podemos fingir.
Daniel Wagner