haskell - El tipo inferido parece detectar un bucle infinito, pero ¿qué está sucediendo realmente?
type-inference type-systems (2)
Ese tipo se puede inferir porque ve que se pasa el resultado de mergesort
to merge
, que a su vez compara los encabezados de las listas con <
, que forma parte de la clase de tipos Ord. Entonces, la inferencia de tipos puede razonar que debe devolver una lista de una instancia de Ord. Por supuesto, ya que en realidad recurre infinitamente, no podemos inferir nada más sobre el tipo que realmente no devuelve.
En la anécdota sobre la inferencia de tipo ML de Andrew Koenig, el autor utiliza la implementación del tipo de combinación como un ejercicio de aprendizaje para ML y se complace en encontrar una inferencia de tipo "incorrecta".
Para mi sorpresa, el compilador reportó un tipo de
''a list -> int list
En otras palabras, esta función de clasificación acepta una lista de cualquier tipo y devuelve una lista de enteros.
Eso es imposible. La salida debe ser una permutación de la entrada; ¿Cómo puede tener un tipo diferente? El lector seguramente encontrará mi primer impulso familiar: ¡Me preguntaba si había descubierto un error en el compilador!
Después de pensarlo un poco más, me di cuenta de que había otra forma en que la función podía ignorar su argumento: tal vez a veces no regresaba en absoluto. De hecho, cuando lo probé, eso es exactamente lo que sucedió:
sort(nil)
devolviónil
, pero clasificar cualquier lista no vacía entraría en un bucle de recursión infinito.
Cuando traducido a 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 infiere un tipo similar:
*Main> :t mergesort
mergesort :: (Ord a) => [t] -> [a]
¿Cómo infiere este tipo el algoritmo Damas-Hindley-Milner ?
Este es ciertamente un ejemplo notable; Un bucle infinito siendo detectado, esencialmente, en tiempo de compilación ! No hay nada especial acerca de la inferencia de Hindley-Milner en este ejemplo; simplemente procede como de costumbre.
Tenga en cuenta que ghc obtiene los tipos de split
y merge
correctamente:
*Main> :t split
split :: [a] -> ([a], [a])
*Main> :t merge
merge :: (Ord t) => [t] -> [t] -> [t]
Ahora, cuando se trata de mergesort
, es, en general, una función t 1 → t 2 para algunos tipos t 1 y t 2 . Entonces ve la primera línea:
mergesort [] = []
y se da cuenta de que t 1 y t 2 deben ser tipos de lista, digamos que t 1 = [t 3 ] y t 2 = [t 4 ]. Entonces mergesort debe ser una función [t 3 ] → [t 4 ]. La siguiente linea
mergesort xs = merge (mergesort p) (mergesort q)
where (p,q) = split xs
le dice que
- xs debe ser una entrada para dividir, es decir, de tipo [a] para algunas a (que ya es, para a = t 3 ).
- Entonces
p
yq
también son del tipo [t 3 ], ya quesplit
es [a] → ([a], [a]) -
mergesort p
, por lo tanto, (recuerde que mergesort se cree que es del tipo [t 3 ] → [t 4 ]) es del tipo [t 4 ]. -
mergesort q
es de tipo [t 4 ] exactamente por el mismo motivo. - Como la
merge
tiene tipo(Ord t) => [t] -> [t] -> [t]
, y las entradas en la expresiónmerge (mergesort p) (mergesort q)
son de tipo [t 4 ], el tipo t 4 debe estar enOrd
. - Finalmente, el tipo de
merge (mergesort p) (mergesort q)
es el mismo que sus entradas, es decir, [t 4 ]. Esto encaja con el tipo conocido anteriormente [t 3 ] → [t 4 ] paramergesort
, por lo que no hay más inferencias que hacer y la parte de "unificación" del algoritmo Hindley-Milner está completa.mergesort
es de tipo [t 3 ] → [t 4 ] con t 4 enOrd
.
Es por eso que obtienes:
*Main> :t mergesort
mergesort :: (Ord a) => [t] -> [a]
(La descripción anterior en términos de inferencia lógica es equivalente a lo que hace el algoritmo, pero la secuencia específica de pasos que sigue el algoritmo es simplemente la que figura en la página de Wikipedia, por ejemplo).