2

我正在玩导管库,并编写了一段示例代码,用于在两个数字(2 和 3)出现在序列中时提取它们。以下是我的代码:

import Data.Conduit
import qualified Data.Conduit.List as CL

source = CL.sourceList [1,2,3,4,5,2,3] :: Source IO Int

-- Extract the consequent 2 and 3 number
extract23 :: Conduit Int IO Int
extract23 = do
  a <- await
  b <- await
  case (a,b) of
    (Just a,Just b) ->
      if a == 2 && b == 3
      then do yield a
              yield b
              extract23
      else extract23
    _ -> return ()

conduit1 :: Conduit Int IO String
conduit1 = CL.map show

sink1 :: Sink String IO ()
sink1 = CL.mapM_ putStrLn

main :: IO ()
main = source $= (extract23 =$= conduit1) $$ sink1

但是当我执行该main函数时,我没有得到任何输出。我所期望的实际上是这样的:

2
3
2
3

知道我做错了什么吗?

4

1 回答 1

4

您的代码连续调用await两次。那就是“给我流中接下来的两个值,不要将它们放回流中。” 当您重复执行此操作时,您实际上是将流分解为 2 值块。使用您的原始列表,您基本上会得到如下所示的元组:

[(1,2),(3,4),(5,2)] -- final 3 is lost since it has no pair

问题是你的 2,3 序列总是落在这些元组中的两个之间。在我看来,您真正想要的算法是:

  • 检查流中的前两个值是否匹配 2,3。
  • 在流中前进一个元素并重复。

目前,您正在向前推进流中的两个元素。

幸运的是,这个问题有一个简单的解决方案:使用await来获取第二个值,同时将其从流中删除,而不是使用peek,它会查看该值并将其放回原处。如果你替换b <- awaitb <- CL.peek,你应该得到你正在寻找的行为。

更新

只是为了提供更多信息。在表面之下,peek是在管道中的两个基元之上实现的:awaitleftover,如下所示:

peek = do
    mx <- await
    case mx of
        Nothing -> return Nothing
        Just x -> do
            leftover x
            return (Just x)

这种提前 1 个元素的能力并没有什么神奇之处。您可以类似地向前看 2 个元素。唯一的技巧是确保以正确的顺序做剩菜:

peek2 = do
    mx <- await
    my <- await
    maybe (return ()) leftover my
    maybe (return ()) leftover mx
    return (mx, my)
于 2014-02-13T11:35:57.640 回答