Você pode corrigir to10plus
usando uma correspondência irrefutável (ou seja, um ~
prefixo) na sua definição de merge
:
merge (a, b) ~(as, bs) = (a:as, b+bs)
A razão da diferença de comportamento entre to10
e to10plus
é que to10
pode retornar o primeiro elemento da lista sem precisar avaliar to10 xs
e, portanto, sem inspecionar xs
.
Por outro lado, antes que possa retornar qualquer coisa, to10plus
deve chamar merge
com sucesso com os argumentos (x, 1)
e to10plus xs
. Para que esta chamada seja bem-sucedida, to10plus xs
deve ser avaliada o suficiente para garantir que ela corresponda ao padrão (as, bs)
usado na definição de merge
, mas essa avaliação requer a inspeção de elementos de xs
, que ainda não estão disponíveis.
Você também pode ter evitado o problema definindo to10plus
um pouco diferente:
to10plus (x:xs) | x < 10 = (x:as,1+bs)
| otherwise = ([], 0)
where (as,bs) = to10plus xs
Aqui, to10plus
pode fornecer ao primeiro elemento x
da primeira parte da tupla sem tentar avaliar as
, e por isso sem tentar correspondência de padrões to10plus xs
com (as,bs)
na where
cláusula. Uma let
cláusula teria feito a mesma coisa:
to10plus (x:xs) | x < 10 = let (as,bs) = to10plus xs in (x:as,1+bs)
| otherwise = ([], 0)
Como o @luqui aponta, essa é uma diferença no tempo das correspondências de padrões feitas por let
e where
declarações:
let (a,b) = expr in body
-- OR --
body where (a,b) = expr
versus case
declarações / definições de função:
case expr of (a,b) -> body
-- OR --
f (a,b) = body -- AND THEN EVALUATING: -- f expr
As instruções let
e where
correspondem aos padrões preguiçosamente, o que significa que expr
não é correspondido ao padrão (a,b)
até que a
ou b
seja avaliado no body
. Em contrapartida, para a case
afirmação, ela expr
corresponde (a,b)
imediatamente, antes body
mesmo de ser examinada. E, dada a definição acima para f
, o argumento to f
será correspondido (a,b)
assim que a expressão f expr
for avaliada sem esperar até a
ou b
ser necessário na função body
. Aqui estão alguns exemplos de trabalho 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
A adição ~
altera o comportamento das case
definições de / function para que a correspondência ocorra somente quando a
ou b
for necessária:
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
ewhere
sempre é preguiçoso, mas os padrões para argumentos são padronizados como estritos, a menos que sejam preguiçosos~
.