type inféré semble détecter une boucle infinie, mais ce qui se passe réellement?
-
11-09-2019 - |
Question
Dans Une anecdote au sujet de l'inférence de type ML , l'auteur utilise la mise en œuvre de rel="noreferrer"> comme un exercice d'apprentissage pour ML et est heureux de trouver un inférence de type « incorrect ».
À ma grande surprise, le compilateur fait état d'un type de
'a list -> int list
En d'autres termes, cette fonction de tri accepte une liste de tout type à tous et renvoie une liste d'entiers.
Cela est impossible. La sortie doit être une permutation de l'entrée; comment peut-il avoir un autre type? Le lecteur va sûrement trouver mon premier courant d'impulsion: je me suis demandé si je l'avais découvert un bogue dans le compilateur
Après avoir réfléchi un peu plus, je réalise qu'il y avait une autre façon dont la fonction pourrait ignorer son argument: peut-être parfois ne pas revenir du tout. En effet, quand je l'ai essayé, c'est exactement ce qui est arrivé.
sort(nil)
ne retournenil
, mais le tri toute liste non vide irait dans une boucle de récursion infinie
Lorsque Haskell traduit
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 infère un type similaire:
*Main> :t mergesort
mergesort :: (Ord a) => [t] -> [a]
Comment le algorithme de Damas-Hindley-Milner déduire ce type ?
La solution
est en effet un exemple remarquable; une boucle infinie étant détectée, essentiellement, à compilation ! Il n'y a rien de spécial sur l'inférence Hindley-Milner dans cet exemple; il procède comme d'habitude.
Notez que GHC obtient correctement les types de split
et merge
:
*Main> :t split
split :: [a] -> ([a], [a])
*Main> :t merge
merge :: (Ord t) => [t] -> [t] -> [t]
Maintenant, quand il vient à mergesort
, il est, en général, une fonction t 1 → t 2 pour certains types t 1 t 2 . Ensuite, il voit la première ligne:
mergesort [] = []
et se rend compte que t 1 et t 2 doit être types de listes, par exemple t 1 = [t 3 ] et t 2 = [t 4 ]. Donc mergesort doit être une fonction [t 3 ] → [t 4 ]. La ligne suivante
mergesort xs = merge (mergesort p) (mergesort q)
where (p,q) = split xs
dit-il que:
- xs doit être une entrée pour diviser, à savoir, de type [a] pour certains un (ce qui est déjà, pour a = t 3 ).
- Alors
p
etq
sont également de type [t 3 ], puisquesplit
est [a] → ([a], [a]) -
mergesort p
, par conséquent, (rappelons que mergesort est censé être de type [t 3 ] → [t 4 ]) est de type [t 4 ]. -
mergesort q
est de type [t 4 ] pour exactement la même raison. - Comme
merge
est de type(Ord t) => [t] -> [t] -> [t]
, et les entrées dans lemerge (mergesort p) (mergesort q)
d'expression sont tous deux de type [t 4 ], type t 4 doit être dans leOrd
. - Enfin, le type de
merge (mergesort p) (mergesort q)
est le même que ses deux entrées, à savoir [t 4 ]. Cela concorde avec le type connu précédemment [t 3 ] → [t 4 ] pourmergesort
, donc il n'y a plus de déductions à faire et la partie "unification" de la algorithme Hindley-Milner est terminé.mergesort
est du type [t 3 ] → [t 4 ] avec t 4 dansOrd
.
C'est pourquoi vous obtenez:
*Main> :t mergesort
mergesort :: (Ord a) => [t] -> [a]
(La description ci-dessus en termes d'inférence logique est équivalent à ce que l'algorithme fait, mais la séquence spécifique d'étapes de l'algorithme suit est simplement celle donnée sur la page de Wikipédia, par exemple).
Autres conseils
Ce type peut être déduit parce qu'il voit que vous passez le résultat de mergesort
à merge
, ce qui compare les têtes des listes avec <
, qui fait partie de la classe Ord. Donc, l'inférence de type raison peut-elle qu'il doit retourner une liste d'une instance d'Ord. Bien sûr, puisqu'il récursif en fait infiniment, on ne peut rien déduire d'autre sur le type, il ne retourne pas vraiment.