Tal como dice el título: ¿qué garantías hay para evaluar una unidad de retorno de la función Haskell? Uno pensaría que no es necesario ejecutar ningún tipo de evaluación en tal caso, el compilador podría reemplazar todas esas llamadas con un ()
valor inmediato a menos que existan solicitudes explícitas de rigor, en cuyo caso el código podría tener que decidir si debería retorno ()
o fondo.
He experimentado con esto en GHCi, y parece que sucede lo contrario, es decir, tal función parece ser evaluada. Un ejemplo muy primitivo sería
f :: a -> ()
f _ = undefined
La evaluación f 1
arroja un error debido a la presencia de undefined
, por lo que definitivamente ocurre alguna evaluación. Sin embargo, no está claro cuán profunda es la evaluación; a veces parece ir tan profundo como es necesario evaluar todas las llamadas a funciones que regresan ()
. Ejemplo:
g :: [a] -> ()
g [] = ()
g (_:xs) = g xs
Este código se repite indefinidamente si se presenta con g (let x = 1:x in x)
. Pero entonces
f :: a -> ()
f _ = undefined
h :: a -> ()
h _ = ()
se puede usar para mostrar que h (f 1)
devuelve ()
, por lo que en este caso no se evalúan todas las subexpresiones de valores unitarios. ¿Cuál es la regla general aquí?
ETA: por supuesto que sé sobre la pereza. Me pregunto qué impide que los escritores de compiladores hagan que este caso en particular sea aún más vago de lo habitual.
ETA2: resumen de los ejemplos: GHC parece tratar ()
exactamente como cualquier otro tipo, es decir, como si hubiera una pregunta sobre qué valor regular que habita el tipo debe devolverse de una función. El hecho de que solo haya uno de esos valores no parece ser (ab) utilizado por los algoritmos de optimización.
ETA3: cuando digo Haskell, me refiero a Haskell según lo definido por el Informe, no a Haskell-the-H-in-GHC. Parece ser una suposición no compartida tan ampliamente como imaginé (que era 'por el 100% de los lectores'), o probablemente habría podido formular una pregunta más clara. Aun así, lamento haber cambiado el título de la pregunta, ya que originalmente preguntaba qué garantías hay para que se llame a tal función.
ETA4: parece que esta pregunta ha seguido su curso, y lo estoy considerando sin respuesta. (Estaba buscando una función de 'pregunta cerrada' pero solo encontré 'responde tu propia pregunta' y como no se puede responder, no tomé esa ruta). Nadie sacó nada del Informe que lo decidiera de cualquier manera , que me siento tentado a interpretar como una fuerte pero no definitiva respuesta "sin garantía para el idioma como tal". Todo lo que sabemos es que la implementación actual de GHC no omitirá la evaluación de dicha función.
Me he encontrado con el problema real al portar una aplicación OCaml a Haskell. La aplicación original tenía una estructura recursiva de muchos tipos, y el código declaraba una serie de funciones que requerían assert_structureN_is_correct
N en 1..6 o 7, cada una de las cuales devolvía la unidad si la estructura era realmente correcta y lanzaba una excepción si no era así. . Además, estas funciones se llamaban entre sí al descomponer las condiciones de corrección. En Haskell, esto se maneja mejor con la Either String
mónada, así que lo transcribí de esa manera, pero la pregunta como cuestión teórica permaneció. Gracias por todas las entradas y respuestas.
fuente
h1::()->() ; h1 () = ()
yh2::()->() ; h2 _ = ()
. Ejecute ambosh1 (f 1)
yh2 (f 1)
, y vea que solo el primero exige(f 1)
.f 1
es "reemplazado" porundefined
en todos los casos.... -> ()
puede 1) terminar y regresar()
, 2) terminar con un error de excepción / tiempo de ejecución y no devolver nada, o 3) divergir (recursión infinita). GHC no optimiza el código, suponiendo que solo 1) puede suceder: sif 1
se solicita, no omite su evaluación y devolución()
. La semántica de Haskell es evaluarla y ver qué sucede entre 1,2,3.()
(el tipo o el valor) en esta pregunta. Todas las mismas observaciones suceden si reemplazas() :: ()
con, digamos, en0 :: Int
todas partes. Todas estas son viejas y aburridas consecuencias de la evaluación perezosa.()
tipo,()
yundefined
.Respuestas:
Parece suponer que el tipo
()
tiene un solo valor posible()
y, por lo tanto, espera que cualquier llamada de función que devuelva un valor de tipo se()
suponga automáticamente que realmente produce el valor()
.Así no es como funciona Haskell. Cada tipo tiene un valor más en Haskell, es decir, ningún valor, error o el llamado "fondo", codificado por
undefined
. Por lo tanto, se está llevando a cabo una evaluación:es equivalente al lenguaje principal
o incluso (*)
y Core's
_Case
está forzando :El valor se ve obligado a la forma normal de la cabeza débil. Esto es parte de la definición del lenguaje.
Haskell no es un lenguaje de programación declarativo .
(*)
print x = putStr (show x)
yshow () = "()"
, por lo que lashow
llamada se puede compilar por completo.De hecho, el valor se conoce de antemano como
()
, e incluso el valor deshow ()
se conoce de antemano como"()"
. Aún así, la semántica de Haskell aceptada exige que el valor de(f 1)
se vea forzado a la forma normal de la cabeza débil antes de proceder a imprimir esa cadena conocida de antemano"()"
.editar: considerar
concat (repeat [])
. ¿Debería ser[]
, o debería ser un bucle infinito?La respuesta de un "lenguaje declarativo" a esto es muy probable
[]
. La respuesta de Haskell es, el bucle infinito .La pereza es la "programación declarativa del pobre", pero todavía no es la realidad .
edit2 :
print $ h (f 1) == _Case (h (f 1)) _Of () -> print ()
y soloh
es forzado, nof
; y para producir su respuestah
no tiene que forzar nada, según su definiciónh _ = ()
.Observaciones de despedida: La pereza puede tener razón de ser, pero no es su definición. La pereza es lo que es. Se define como todos los valores que inicialmente son thunks que son forzados a WHNF de acuerdo con las demandas que provienen
main
. Si ayuda a evitar el fondo en un determinado caso específico de acuerdo con sus circunstancias específicas, entonces lo hace. Si no, no. Eso es todo.Ayuda a implementarlo usted mismo, en su idioma favorito, para tener una idea de ello. Pero también podemos rastrear la evaluación de cualquier expresión nombrando cuidadosamente todos los valores intermedios a medida que surgen.
A juzgar por el informe , tenemos
entonces
y con
Continúa
Ahora, la sección 3.17.3 Semántica formal de coincidencia de patrones del informe dice
y caso
(r)
en la Figura 3.2 estados()
es el constructor de datos de arity 0, por lo que es lo mismo quey el resultado general de la evaluación es así
⊥
.fuente
case
de Core, y estaba ignorando el agujero enorme. :) He editado para mencionar el Core.show
invocada porprint
? Algo así comoshow x = case x of () -> "()"
case
Core, no a Haskell en sí. Haskell se traduce al Core, que tiene un forzadocase
, AFAIK. Tienes razóncase
en que en Haskell no se está forzando por sí solo. Podría escribir algo en Scheme o ML (si pudiera escribir ML), o pseudocódigo.print
fuerza tanto como sea necesario para imprimir. no mira el tipo, los tipos se han ido, borrado, para cuando se ejecuta el programa, la subrutina de impresión correcta ya está seleccionada y compilada, según el tipo, en el momento de la compilación; esa subrutina seguirá forzando su valor de entrada a WHNF en tiempo de ejecución, y si no está definida, causará un error.