Gracias a la evaluación perezosa, un programa Haskell no hace (casi no puede ) hacer lo que parece.
Considere este programa:
main = putStrLn (show (quicksort [8, 6, 7, 5, 3, 0, 9]))
En un lenguaje ávido, primero quicksortcorrería, luego show, luego putStrLn. Los argumentos de una función se calculan antes de que esa función comience a ejecutarse.
En Haskell, es todo lo contrario. La función comienza a ejecutarse primero. Los argumentos solo se calculan cuando la función realmente los usa. Y un argumento compuesto, como una lista, se calcula una pieza a la vez, a medida que se utiliza cada pieza.
Entonces, lo primero que sucede en este programa es queputStrLn comienza a ejecutarse.
La implementación de GHC deputStrLn funciona copiando los caracteres del argumento String en un búfer de salida. Pero cuando entra en este bucle, showaún no se ha ejecutado. Por lo tanto, cuando va a copiar el primer carácter de la cadena, Haskell evalúa la fracción de showy las quicksortllamadas necesarias para calcular ese carácter . Luego putStrLnpasa al siguiente personaje. Por lo que la ejecución de los tres funciones- putStrLn, showy quicksort- se intercalan. quicksortse ejecuta de forma incremental, dejando un gráfico de procesadores no evaluados a medida que avanza para recordar dónde se quedó.
Ahora bien, esto es tremendamente diferente de lo que podría esperar si está familiarizado con, ya sabe, cualquier otro lenguaje de programación. No es fácil visualizar cómo se quicksortcomporta realmente Haskell en términos de accesos a la memoria o incluso el orden de las comparaciones. Si solo pudiera observar el comportamiento, y no el código fuente, no reconocería lo que está haciendo como una clasificación rápida .
Por ejemplo, la versión C de quicksort particiona todos los datos antes de la primera llamada recursiva. En la versión Haskell, el primer elemento del resultado se calculará (e incluso podría aparecer en su pantalla) antes de que termine de ejecutarse la primera partición, de hecho, antes de que se realice ningún trabajo greater.
PD: El código de Haskell sería más parecido a una ordenación rápida si hiciera el mismo número de comparaciones que la ordenación rápida; el código tal como está escrito hace el doble de comparaciones porque lessery greaterse especifican para ser calculados de forma independiente, haciendo dos escaneos lineales a través de la lista. Por supuesto, en principio es posible que el compilador sea lo suficientemente inteligente como para eliminar las comparaciones adicionales; o el código podría cambiarse para usarData.List.partition .
PPS El ejemplo clásico de que los algoritmos de Haskell no se comportan como se esperaba es el tamiz de Eratóstenes para calcular números primos.