我试图理解pipes tutorial关于ListT
提出的一个例子:
import Pipes
import qualified Pipes.Prelude as P
input :: Producer String IO ()
input = P.stdinLn >-> P.takeWhile (/= "quit")
name :: ListT IO String
name = do
firstName <- Select input
lastName <- Select input
return (firstName ++ " " ++ lastName)
如果运行上面的示例,我们得到如下输出:
>>> runEffect $ every name >-> P.stdoutLn
Daniel<Enter>
Fischer<Enter>
Daniel Fischer
Wagner<Enter>
Daniel Wagner
quit<Enter>
Donald<Enter>
Stewart<Enter>
Donald Stewart
Duck<Enter>
Donald Duck
quit<Enter>
quit<Enter>
>>>
似乎:
Select input
定义)在读取输入时轮流(可能是非确定性的)。 quit
将允许重新绑定名字。同样,我不明白为什么firstName
将绑定到用户输入的第一个值。quit
将终止该程序。但是,我希望只需要输入quit
两次才能退出程序(可能与其他输入交替)。我遗漏了上述例子的工作原理,但我看不清楚。
答案 0 :(得分:9)
当你运行它(在GHCi上)时,你输入的第一个名字将被绑定 只有第二个会改变。我希望这两个生产者 (由
Select input
定义)将轮流(也许 非确定地)在阅读输入时。
ListT
不起作用。相反,它是“深度优先”。每次获得名字时,它都会重新开始阅读姓氏的整个列表。
该示例不会这样做,但每个姓氏列表可能取决于先前已读取的名字。像这样:
input' :: String -> Producer String IO ()
input' msg =
(forever $ do
liftIO $ putStr msg
r <- liftIO $ getLine
yield r
) >-> P.takeWhile (/= "quit")
name' :: ListT IO String
name' = do
firstName <- Select input
lastName <- Select $ input' $ "Enter a last name for " ++ firstName ++ ": "
return (firstName ++ " " ++ lastName)
输入退出一次将允许重新绑定第一个名称。再一次,我 无法理解为什么firstName将绑定到输入的第一个值 用户。
如果我们正在阅读姓氏并遇到退出命令,那么“分支”终止,我们回到上面的级别,从列表中读取另一个名字。只有在使用名字后才能重新创建读取姓氏的“有效列表”。
连续两次输入
quit
将终止该程序。但是,我 我希望只需输入两次quit
即可退出 程序(可能与其他输入交替)。
请注意,在最开始输入一个退出也将终止该程序,因为我们正在“关闭”顶级名字列表。
基本上,每次输入quit
时,您都会关闭当前分支并上升到“搜索树”中的某个级别。每次输入名字时,你都会进入一个级别。
答案 1 :(得分:0)
要添加@ danidiaz的最佳答案,可以再次提示name
提示firstName
,而不是仅仅继续询问{ {1}}秒。
您可以做的是使用lastName
的{{1}}实例,尤其是
MonadZip
因此,您可以将Monad m => ListT m
定义为
mzip :: Monad m => ListT m a -> ListT m b -> ListT m (a, b).
你得到了交替的行为。
除此之外,您还可以使用name
和name :: ListT IO String
name = do
(firstName, lastName) <- mzip (Select input) (Select input)
return (firstName ++ " " ++ lastName)
扩展程序。使用这些,MonadComprehensions
的两个版本变为
ParallelListComp