【问题标题】:How to turn a pull based pipe into a push based one?如何将基于拉的管道变成基于推的管道?
【发布时间】:2016-12-01 21:15:02
【问题描述】:

默认情况下,管道是基于拉的。这是由于运算符>-> 是通过+>> 实现的,bind 运算符对于他的拉动类别很有意义。我的理解是,这意味着如果你有producer >-> consumer这样的代码,首先会调用消费者的body,然后一旦等待数据,就会调用生产者。

我在pipes 文档here 中看到,您可以使用Pipes.Core 中的代码(reflect .) 将基于拉的管道转换为基于推送的管道。这意味着相反(如果我错了,请纠正我)在producer >-> consumer 上面的代码中,生产者首先运行,产生一个值,然后消费者尝试消费。这似乎真的很有用,我想知道怎么做。

我还在here 的讨论中看到>-> 没有基于推送的对应物,因为它很容易扭转任何管道(我假设使用反射?),但我真的不知道如何做它或找到任何例子。

这是我尝试过的一些代码:

stdin :: Producer String IO r
stdin = forever $ do
  lift $ putStrLn "stdin"
  str <- lift getLine
  yield str

countLetters :: Consumer String IO r
countLetters = forever $ do
  lift $ putStrLn "countLetters"
  str <- await
  lift . putStrLn . show . length $ str

-- this works in pull mode
runEffect (stdin >-> countLetters)

-- equivalent to above, works
runEffect ((\() -> stdin) +>> countLetters)

-- push based operator, doesn't do what I hoped
runEffect (stdin >>~ (\_ -> countLetters))

-- does not compile
runEffect (countLetters >>~ (\() -> stdin))

【问题讨论】:

    标签: haskell haskell-pipes


    【解决方案1】:
    -- push based operator, doesn't do what I hoped
    runEffect (stdin >>~ (\_ -> countLetters))
    

    我认为这里的问题是,当生产者按预期首先运行时,第一个产生的值被丢弃了。比较...

    GHCi> runEffect (stdin >-> countLetters)
    countLetters
    stdin
    foo
    3
    countLetters
    stdin
    glub
    4
    countLetters
    stdin
    

    ...与:

    GHCi> runEffect (stdin >>~ (\_ -> countLetters))
    stdin
    foo
    countLetters
    stdin
    glub
    4
    countLetters
    stdin
    

    Gabriella Gonzalez's answerthis question 详细讨论了这个问题。它归结为您提供给(&gt;&gt;~) 的函数的参数如何是基于推送的流程中的“驱动”输入,因此如果您将const 去掉,您最终会丢弃第一个输入。解决方案是相应地重塑countLetters

    countLettersPush :: String -> Consumer String IO r
    countLettersPush str = do
      lift $ putStrLn "countLetters"
      lift . putStrLn . show . length $ str
      str' <- await
      countLettersPush str'
    
    GHCi> runEffect (stdin >>~ countLettersPush)
    stdin
    foo
    countLetters
    3
    stdin
    glub
    countLetters
    4
    stdin
    

    我还在here 的讨论中看到&gt;-&gt; 没有基于推送的对应物,因为它很容易扭转任何管道(我假设使用反射?)

    我不完全确定自己的立场,但似乎并不完全适用于上述解决方案。我们可以做的,现在我们已经让基于推送的流程正常工作,使用reflect 将其转回基于拉取的流程:

    -- Preliminary step: switching to '(>~>)'.
    stdin >>~ countLettersPush
    (const stdin >~> countLettersPush) ()
    
    -- Applying 'reflect', as the documentation suggests.
    reflect . (const stdin >~> countLettersPush)
    reflect . const stdin <+< reflect . countLettersPush
    const (reflect stdin) <+< reflect . countLettersPush
    
    -- Rewriting in terms of '(+>>)'.
    (reflect . countLettersPush >+> const (reflect stdin)) ()
    reflect . countLettersPush +>> reflect stdin
    

    这确实是基于拉的,因为流是由reflect stdin驱动的,下游Client

    GHCi> :t reflect stdin
    reflect stdin :: Proxy String () () X IO r
    GHCi> :t reflect stdin :: Client String () IO r
    reflect stdin :: Client String () IO r :: Client String () IO r
    

    但是,该流程涉及向上游发送Strings,因此不能用(&gt;-&gt;) 表示,也就是说,仅下游:

    GHCi> -- Compare the type of the second argument with that of 'reflect stdin'
    GHCi> :t (>->)
    (>->)
      :: Monad m =>
         Proxy a' a () b m r -> Proxy () b c' c m r -> Proxy a' a c' c m 
    

    【讨论】:

      猜你喜欢
      • 2019-10-04
      • 2019-08-02
      • 2010-12-29
      • 1970-01-01
      • 1970-01-01
      • 2021-08-20
      • 1970-01-01
      • 2020-11-01
      • 2020-10-02
      相关资源
      最近更新 更多