GHC implementa Haskell como una máquina de reducción de gráfico. Imagina tu programa como un gráfico con cada valor como un nodo, y líneas desde él a cada valor de que depende ese valor. Excepto que somos perezosos, así que realmente comienzas con un solo nodo, y para evaluar ese nodo, GHC tiene que "ingresarlo" y abrirlo a una función con argumentos. Luego reemplaza la llamada de función con el cuerpo de la función, e intenta reducirla lo suficiente como para ponerla en la forma normal de la cabeza, etc.
Lo anterior es muy manual y estoy seguro de que eludir algunos detalles necesarios en aras de la brevedad.
En cualquier caso, cuando GHC ingresa un valor, generalmente lo reemplaza con un agujero negro mientras se evalúa el nodo (o, dependiendo de su terminología, mientras se reduce el cierre) Esto tiene varios propósitos. Primero, conecta una posible fuga de espacio. Si el nodo hace referencia a un valor que no se usa en ninguna otra parte, el agujero negro permite que ese valor sea recolectado como basura incluso mientras el nodo está siendo evaluado. En segundo lugar, esto evita ciertos tipos de trabajo duplicado, ya que en un entorno de subprocesos múltiples, dos subprocesos pueden intentar ingresar el mismo valor. El agujero negro hará que el segundo hilo bloquee en lugar de evaluar el valor que ya se está evaluando. Finalmente, esto permite una forma limitada de detección de bucles, ya que si un hilo intenta volver a ingresar en su propio agujero negro, podemos lanzar una excepción.
Aquí hay un poco de una explicación más metafórica. Si tengo una serie de instrucciones que mueven una tortuga (en el logotipo) alrededor de la pantalla, no hay una sola forma de decir qué forma producirán, o si esa forma termina sin ejecutarlas. Pero si, al correrlos, noto que el camino de la tortuga se ha cruzado, puedo indicarle al usuario "¡Ajá !, la tortuga se ha cruzado en su camino". Así que sé que la tortuga ha alcanzado un punto en el que ha estado antes; si la ruta es un circuito mediante la evaluación de los nodos de un gráfico, eso nos indica que estamos en un bucle. Sin embargo, la tortuga también puede entrar, por ejemplo, en una espiral en expansión. Y nunca terminará, pero tampoco cruzará su camino anterior.
Por lo tanto, debido al uso de agujeros negros, por varias razones, tenemos alguna noción de una "ruta" marcada que ha seguido la evaluación. Y si el camino se cruza a sí mismo, podemos decir y lanzar una excepción. Sin embargo, hay un millón de formas en que las cosas divergen que no involucran el camino que se cruza a sí mismo. Y en esos casos, no podemos decir, y no lanzar una excepción.
Para detalles técnicos súper geek sobre la implementación actual de agujeros negros, vea la charla de Simon Marlow del Taller de Implementadores Haskell reciente, "Programar evaluación diferida en multinúcleo" en la parte inferior de http://haskell.org/haskellwiki/HaskellImplementorsWorkshop/2010.
Gracias bdonlan. Acabo de actualizar mi pregunta un poco. Entonces, ¿cuál es la razón detrás de tratar de detectar bucles infinitos? ¿Por qué hay una excepción en el tiempo de ejecución mientras no hay una advertencia en tiempo de compilación? – Phil
@Po, respuesta actualizada :) – bdonlan
El término usual para reemplazar tales cosas con errores es "blackholing". También cf, por ejemplo, [fixIO] (http://www.haskell.org/ghc/docs/latest/html/libraries/base/src/System-IO.html#fixIO), que hace esencialmente lo mismo. – barsoap