いつHaskellを使ってリストをレイジーに読むことができますか?


8

<<loop>>ここで無限ループ()ランタイムエラーが発生するのはなぜですか?

ファイル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>>

回答:


7

の定義でto10plus反駁できない一致(つまり、~プレフィックス)を使用して修正できますmerge

merge (a, b) ~(as, bs) = (a:as, b+bs)

間の動作の違いの理由to10to10plusそれがあるto10評価することなく、リストの最初の要素を返すことができto10 xs検査なしとなりますxs

対照的に、何かを返す前に、とを引数としてto10plus正常に呼び出す必要があります。この呼び出しを成功させるには、がの定義で使用されているパターンと一致することを確認するために十分に評価する必要がありますが、その評価には、まだ使用できないの要素の検査が必要です。merge(x, 1)to10plus xsto10plus xs(as, bs)mergexs

to10plus少し異なる方法で定義することで問題を回避することもできます。

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

ここでは、を評価しようとせずに、タプルの最初の部分のto10plus最初の要素xを提供できます。asしたがって、節内でパターン一致to10plus xsを試みようとはしません。句は、同じことをやっているだろう。(as,bs)wherelet

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

@luquiが指摘するように、これはletand whereステートメントによって行われるパターン一致のタイミングの違いです。

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

caseステートメントと関数の定義の比較:

case expr of (a,b) -> body
-- OR --
f (a,b) = body  -- AND THEN EVALUATING: -- f expr

letそしてwhereステートメントがあることを意味し、遅延したパターンに一致するexprパターンに一致しない(a,b)までabで評価されていますbody。対照的に、caseステートメントの場合、exprは調べられる(a,b)前にすぐに照合さbodyれます。また、の上記の定義が与えられた場合f、への引数は、式が評価されると、関数で必要になるまで、または関数で必要になるまで待つことなくf一致します。ここにいくつかの実例を示します:(a,b)f exprabbody

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

追加~すると、case/関数定義の動作が変更され、必要な場合aまたはb必要な場合にのみマッチングが行われます。

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"

3
主な違いは自明ではなく、おそらく述べるべきです。パターンはletと一致し、where常に遅延ですが、引数のパターンは、を使用して遅延させない限り、デフォルトでstrictになります~
ルキ

KA Buhrとluquiに感謝します!
Daniel Patru
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.