Puede solucionarlo to10plus
usando una coincidencia irrefutable (es decir, un ~
prefijo) en su definición de merge
:
merge (a, b) ~(as, bs) = (a:as, b+bs)
La razón de la diferencia en el comportamiento entre to10
y to10plus
es que to10
puede devolver el primer elemento de la lista sin tener que evaluar to10 xs
y así sin inspeccionar xs
.
Por el contrario, antes de que pueda devolver algo, to10plus
debe llamar merge
con éxito con los argumentos (x, 1)
y to10plus xs
. Para que esta llamada to10plus xs
tenga éxito, debe evaluarse lo suficiente como para garantizar que coincida con el patrón (as, bs)
utilizado en la definición de merge
, pero esa evaluación requiere la inspección de elementos de xs
, que aún no están disponibles.
También podría haber evitado el problema definiendo to10plus
un poco diferente:
to10plus (x:xs) | x < 10 = (x:as,1+bs)
| otherwise = ([], 0)
where (as,bs) = to10plus xs
Aquí, to10plus
puede proporcionar el primer elemento x
de la primera parte de la tupla sin intentar evaluar as
, y así sin tratar de coincidir to10plus xs
con el patrón (as,bs)
en la where
cláusula. Una let
cláusula habría hecho lo mismo:
to10plus (x:xs) | x < 10 = let (as,bs) = to10plus xs in (x:as,1+bs)
| otherwise = ([], 0)
Como señala @luqui, esta es una diferencia en el tiempo para las coincidencias de patrones realizadas por let
y where
declaraciones:
let (a,b) = expr in body
-- OR --
body where (a,b) = expr
versus case
declaraciones / definiciones de funciones:
case expr of (a,b) -> body
-- OR --
f (a,b) = body -- AND THEN EVALUATING: -- f expr
Las declaraciones let
y where
coinciden con los patrones de manera perezosa, lo que significa que expr
no coinciden con el patrón (a,b)
hasta que a
o b
se evalúan en el body
. Por el contrario, para la case
declaración, expr
se compara de (a,b)
inmediato, body
incluso antes de que se examine. Y dada la definición anterior de f
, el argumento to f
será comparado (a,b)
tan pronto como f expr
se evalúe la expresión sin esperar hasta a
o b
sea necesaria en la función body
. Aquí hay algunos ejemplos de trabajo para ilustrar:
ex1 = let (a,b) = undefined in print "okay"
ex2 = print "also okay" where (a,b) = undefined
ex3 = case undefined of (a,b) -> print "not okay"
ex4 = f undefined
f (a,b) = print "also not okay"
main = do
ex1 -- works
ex2 -- works
ex3 -- fails
ex4 -- fails
Adición de ~
cambios del comportamiento para case
las definiciones de funciones / por lo que el juego se lleva a cabo sólo cuando a
o b
se necesitan para:
ex5 = case undefined of ~(a,b) -> print "works fine"
ex6 = g undefined
g ~(a,b) = print "also works fine"
ex7 = case undefined of ~(a,b) -> print $ "But trying " ++ show (a+b) ++ " would fail"
let
ywhere
siempre es vago, pero los patrones para los argumentos son por defecto estrictos a menos que se hagan vagos~
.