Grâce à l'évaluation paresseuse, un programme Haskell ne le fait pas (presque pas ) ce qu'il semble faire.
Considérez ce programme:
main = putStrLn (show (quicksort [8, 6, 7, 5, 3, 0, 9]))
Dans une langue enthousiaste, d'abord quicksort
courrait, puis show
, puis putStrLn
. Les arguments d'une fonction sont calculés avant que cette fonction ne commence à s'exécuter.
Dans Haskell, c'est le contraire. La fonction démarre en premier. Les arguments ne sont calculés que lorsque la fonction les utilise réellement. Et un argument composé, comme une liste, est calculé un morceau à la fois, car chaque morceau est utilisé.
Donc, la première chose qui se passe dans ce programme est queputStrLn
démarre.
L'implémentation de GHCputStrLn
fonctionne en copiant les caractères de l'argument String dans un tampon de sortie. Mais quand il entre dans cette boucle, show
n'a pas encore exécuté. Par conséquent, lorsqu'il va copier le premier caractère de la chaîne, Haskell évalue la fraction des appels show
et quicksort
nécessaires pour calculer ce caractère . Puis putStrLn
passe au personnage suivant. Ainsi , l'exécution des trois fonctions- putStrLn
, show
et quicksort
- est entrelacée. quicksort
s'exécute de manière incrémentielle, laissant un graphique de thunks non évalués pendant qu'il se souvient où il s'est arrêté.
Maintenant, c'est très différent de ce à quoi vous pourriez vous attendre si vous connaissez, vous savez, n'importe quel autre langage de programmation. Il n'est pas facile de visualiser comment quicksort
se comporte réellement dans Haskell en termes d'accès à la mémoire ou même l'ordre des comparaisons. Si vous pouviez seulement observer le comportement, et non le code source, vous ne reconnaîtrez pas ce qu'il fait comme un tri rapide .
Par exemple, la version C de quicksort partitionne toutes les données avant le premier appel récursif. Dans la version Haskell, le premier élément du résultat sera calculé (et pourrait même apparaître sur votre écran) avant la fin de l'exécution de la première partition - en fait avant que tout travail ne soit effectué greater
.
PS Le code Haskell serait plus rapide s'il effectuait le même nombre de comparaisons que quicksort; le code tel qu'il est écrit fait deux fois plus de comparaisons car lesser
il greater
est spécifié pour être calculé indépendamment, en effectuant deux balayages linéaires dans la liste. Bien sûr, il est possible en principe que le compilateur soit suffisamment intelligent pour éliminer les comparaisons supplémentaires; ou le code pourrait être changé pour utiliser Data.List.partition
.
PPS L'exemple classique des algorithmes de Haskell se révélant ne pas se comporter comme prévu est le tamis d'Eratosthène pour le calcul des nombres premiers.