Tipo inferir parece detectar um loop infinito, mas o que está realmente acontecendo?
-
11-09-2019 - |
Pergunta
Uma anedota de Em Andrew Koenig sobre o tipo ML inferência , a usos autor implementação de merge sort como um exercício de aprendizagem para ML e tem o prazer de encontrar um “incorreto” inferência de tipos.
Para minha surpresa, o compilador relatado um tipo de
'a list -> int list
Em outras palavras, essa função tipo aceita uma lista de qualquer tipo em tudo e retorna uma lista de números inteiros.
Isso é impossível. A saída deve ser uma permutação de entrada; como pode, eventualmente, ter um tipo diferente? O leitor certamente irá encontrar o meu primeiro impulso familiar: Eu me perguntava se eu tinha descoberto um bug no compilador
Depois de pensar um pouco mais, eu percebi que não havia outra maneira em que a função poderia ignorar o seu argumento: talvez, por vezes, não retornou em tudo. Na verdade, quando eu tentei, que é exatamente o que aconteceu:.
sort(nil)
fez retornonil
, mas classificar qualquer lista não-vazia iria entrar em um loop de recursão infinita ??p>
Quando traduzido para Haskell
split [] = ([], [])
split [x] = ([x], [])
split (x:y:xs) = (x:s1, y:s2)
where (s1,s2) = split xs
merge xs [] = xs
merge [] ys = ys
merge xx@(x:xs) yy@(y:ys)
| x < y = x : merge xs yy
| otherwise = y : merge xx ys
mergesort [] = []
mergesort xs = merge (mergesort p) (mergesort q)
where (p,q) = split xs
GHC infere um tipo similar:
*Main> :t mergesort
mergesort :: (Ord a) => [t] -> [a]
Como o Damas-Hindley-Milner algoritmo inferir este tipo ?
Solução
Este é realmente um exemplo notável; um loop infinito ser detectado, essencialmente, em tempo de compilação ! Não há nada de especial sobre a inferência Hindley-Milner neste exemplo; ele só procede como de costume.
Note que ghc recebe os tipos de split
e merge
corretamente:
*Main> :t split
split :: [a] -> ([a], [a])
*Main> :t merge
merge :: (Ord t) => [t] -> [t] -> [t]
Agora, quando se trata de mergesort
, é, em geral, uma função de t 1 ? t 2 para alguns tipos de t 1 e t 2 . Em seguida, ele vê a primeira linha:
mergesort [] = []
e percebe que t 1 e t 2 devem ser do tipo da lista, dizem t 1 = [t 3 ] e t 2 = [t 4 ]. Então mergesort deve ser uma função [t 3 ] ? [t 4 ]. A próxima linha
mergesort xs = merge (mergesort p) (mergesort q)
where (p,q) = split xs
diz-se que:
- xs deve ser uma entrada de separação, isto é, do tipo [a] para algumas um (o que já é, para a = t 3 ).
- Assim
p
eq
também são do tipo [t 3 ], desdesplit
é [a] ? ([a], [a]) -
mergesort p
, por conseguinte, (lembrar que mergesort acredita-se ser do tipo [t 3 ] ? [t 4 ]) é do tipo [t 4 ]. -
mergesort q
é do tipo [t 4 ] para exatamente a mesma razão. - Como
merge
tem tipo(Ord t) => [t] -> [t] -> [t]
, e as entradas namerge (mergesort p) (mergesort q)
expressão são tanto do tipo [t 4 ], do tipo t 4 devem estar emOrd
. - Finalmente, o tipo de
merge (mergesort p) (mergesort q)
é o mesmo que ambas as suas entradas, nomeadamente [t 4 ]. Isso se encaixa com o tipo anteriormente conhecido [t 3 ] ? [t 4 ] paramergesort
, para que não haja mais inferências a serem feitas e a parte "unificação" do algoritmo de Hindley-Milner está completa.mergesort
é do tipo [t 3 ] ? [t 4 ] com t 4 emOrd
.
É por isso que você tem:
*Main> :t mergesort
mergesort :: (Ord a) => [t] -> [a]
(A descrição acima em termos de inferência lógica é equivalente ao que o algoritmo faz, mas a seqüência específica de passos do algoritmo segue é simplesmente que, dada na página da Wikipedia, por exemplo.)
Outras dicas
Esse tipo pode ser inferido porque vê que você passe o resultado de mergesort
para merge
, que por sua vez compara as cabeças das listas com <
, que faz parte do typeclass Ord. Assim, o tipo de razão inferência lata que ele deve retornar uma lista de uma instância de Ord. Claro, desde que ele realmente recurses infinitamente, não podemos inferir que qualquer outra coisa sobre o tipo que na verdade não voltar.