Quando posso confiar em Haskell para ler uma lista preguiçosamente?

8

Por que recebo <<loop>>aqui um erro de tempo de execução infinito loop ( )?

arquivo feedback.hs:

plus1 :: [Int]->[Int] -- add 1 to input stream
plus1 [] = []
plus1 (x:xs) = (x+1): plus1 xs

to10 :: [Int] -> [Int] -- stop the input stream when it gets to 10
to10 (x:xs) | x < 10 = x : to10 xs
            | otherwise = []

to10plus :: [Int] -> ([Int], Int) -- like to10 but also return the count
to10plus (x:xs) | x < 10 = (x, 1) `merge` (to10plus xs)
            | otherwise = ([], 0)
  where merge (a, b) (as, bs) = (a:as, b+bs)

main = do
  let out = plus1 $ 1: to10 out
  putStrLn $ show out -- gives [2,3,4,5,6,7,8,9,10]


  let out = plus1 $ 1: out2
      out2 = to10 out
  putStrLn $ show out -- same as above

  let out = plus1 $ 1: out2
      (out2, count) = to10plus out
  putStrLn $ show (out, count) -- expect ([2,3,4,5,6,7,8,9,10], 8) 
                               -- but get runtime error: <<loop>>
$ ghc feedback.hs 
[1 of 1] Compiling Main             ( feedback.hs, feedback.o )
Linking feedback ...
$ ./feedback
[2,3,4,5,6,7,8,9,10]
[2,3,4,5,6,7,8,9,10]
feedback: <<loop>>
Daniel Patru
fonte

Respostas:

7

Você pode corrigir to10plususando 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 to10e to10plusé que to10pode retornar o primeiro elemento da lista sem precisar avaliar to10 xse, portanto, sem inspecionar xs.

Por outro lado, antes que possa retornar qualquer coisa, to10plusdeve chamar mergecom sucesso com os argumentos (x, 1)e to10plus xs. Para que esta chamada seja bem-sucedida, to10plus xsdeve 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 to10plusum pouco diferente:

to10plus (x:xs) | x < 10 = (x:as,1+bs)
                | otherwise = ([], 0)
  where (as,bs) = to10plus xs

Aqui, to10pluspode fornecer ao primeiro elemento xda primeira parte da tupla sem tentar avaliar as, e por isso sem tentar correspondência de padrões to10plus xscom (as,bs)na wherecláusula. Uma letclá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 lete wheredeclarações:

let (a,b) = expr in body
-- OR --
body where (a,b) = expr

versus casedeclaraçõ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 lete wherecorrespondem aos padrões preguiçosamente, o que significa que exprnão é correspondido ao padrão (a,b)até que aou bseja avaliado no body. Em contrapartida, para a caseafirmação, ela exprcorresponde (a,b)imediatamente, antes bodymesmo de ser examinada. E, dada a definição acima para f, o argumento to fserá correspondido (a,b)assim que a expressão f exprfor avaliada sem esperar até aou bser 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 casedefinições de / function para que a correspondência ocorra somente quando aou bfor 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"
KA Buhr
fonte
3
A principal diferença está longe de ser evidente e provavelmente deve ser declarada: o padrão corresponde lete wheresempre é preguiçoso, mas os padrões para argumentos são padronizados como estritos, a menos que sejam preguiçosos ~.
luqui 12/12/19
Obrigado KA Buhr e luqui!
Daniel Patru