Pipes.Binary.decode - StateT用于什么?

时间:2014-02-01 21:16:49

标签: haskell haskell-pipes

我正在尝试使用管道和基于它构建的各种库来编写基本网络服务器。预期的流程是:

从socket获取bytestring - >使用二进制解码 - >服务器逻辑在这里 - >发送响应socket

我认为这将是:

fromSocket s 4096 >-> decode >-> serverLogic >-> toSocket s

pipes-binary有一个decode和一个decodeMany,但我不确定我是否理解其中的差异,而且我不知道如何使用decode。为什么decodeMany将上游管道作为参数而不是使用>->将其链接起来?你如何使用decodeStateT是什么,我的管道最终应该是什么样的?

2 个答案:

答案 0 :(得分:5)

StateT (Producer a m r) m x成语来自pipes-parse's "Low-level Parsers"。这通常意味着库正在使用drawunDrawProducer中提取值,并在未使用时返回它们。它是解析可能发生故障的重要组成部分。它还需要StateT图层来指示管道被选择性地排空并以有状态的方式重新填充。

-- | Draw one element from the underlying Producer, 
-- returning Left if the Producer is empty
draw :: Monad m => StateT (Producer a m r) m (Either r a)

-- | Push back an element onto the underlying Producer
unDraw :: Monad m => a -> StateT (Producer a m r) m ()

那么这对decodedecodeMany意味着什么?如果我们看一些这些函数的简化类型

-- for (Monad m, Binary b)

decode     :: StateT (Producer ByteString m r) m (Maybe b)
decodeMany :: Producer ByteString m r 
           -> Producer' b m (Either (Producer ByteString m r) r)

我们首先看到decode drawing ByteString来自Producer ByteString的{​​{1}}个状态,以便尝试解析b。由于ByteString上的块边界可能与解析边界不对齐,因此在StateT中执行此操作非常重要,这样剩余的块可以unDraw - 重新导入{{1} }}

ProducerdecodeMany之上构建并尝试重复decode decode关闭输入生产者返回剩余的b Producer ByteString失败了。

长话短说,由于需要unDraw剩余的ByteString块,我们可以将这些内容组合成一个(>->)链。如果你想这样做,你可以使用decodeMany之类的东西来转换生产者,然后将结果链接起来,但是你需要仔细处理错误情况。

答案 1 :(得分:3)

我想通过回答你关于为什么解码器不是Pipe的其他问题来补充J. Abrahamson的答案。

Pipe与类似:

之类的区别
pipe :: Pipe a b m r

...和Producer之间的函数(我称之为“getter”):

getter :: Producer a m r -> Producer b m r

...是Pipe可用于转换Producer s,Consumer和其他Pipe s:

(>-> pipe) :: Producer a m r -> Producer b m r

(>-> pipe) :: Pipe x a m r -> Pipe x b m r

(pipe >->) :: Consumer b m r -> Consumer a m r

(pipe >->) :: Pipe b y m r -> Pipe a y m r

...而“getter”只能转换Producer s。有些东西无法使用Pipe正确建模,剩下的就是其中之一。

conduit声称使用Conduit s(conduit模拟Pipe s)对剩余物进行建模,但这样做是错误的。我已经汇总了一个简单的例子来说明原因。首先,只需为peek实现conduit函数:

import Control.Monad.Trans.Class (lift)
import Data.Conduit
import Data.Conduit.List (isolate, sourceList)

peek :: Monad m => Sink a m (Maybe a)
peek = do
    ma <- await
    case ma of
        Nothing -> return ()
        Just a  -> leftover a
    return ma

对于像这样的简单案例,这可以正常工作:

source :: Monad m => Source m Int
source = sourceList [1, 2]

sink1 :: Show a => Sink a IO ()
sink1 = do
    ma1 <- peek
    ma2 <- peek
    lift $ print (ma1, ma2)

这将返回源的第一个元素两次:

>>> source $$ sink1
(Just 1,Just 1)

...但如果你在Conduit的上游组成Sink,那么水槽推回的任何剩余物都会不可逆转地丢失:

sink2 :: Show a => Sink a IO ()
sink2 = do
    ma1 <- isolate 10 =$ peek
    ma2 <- peek
    lift $ print (ma1, ma2)

现在第二个peek错误地返回2

>>> source $$ sink2
(Just 1,Just 2)

另请注意,pipes-parse刚刚发布了今天发布的新主要版本,这简化了API并添加了一个可以阅读here的广泛教程。

这个新API正确地传播剩余的上游剩余物。以下是pipes

的类似示例
import Lens.Family.State.Strict (zoom)
import Pipes
import Pipes.Parse
import Prelude hiding (splitAt)

parser :: Show a => Parser a IO ()
parser = do
    ma1 <- zoom (splitAt 10) peek
    ma2 <- peek
    lift $ print (ma1, ma2)

producer :: Monad m => Producer Int m ()
producer = each [1, 2]

即使第一个peek也仅限于前10个值,它也会正确地取消第一个值并使其可用于第二个peek

>>> evalStateT parser producer
(Just 1,Just 1)

从概念上讲,pipes-parse“根据Producer s”思考的原因是因为否则剩余的概念没有明确定义。如果你没有清楚地定义你的来源是什么,你就无法清楚地说明剩余价值应该去哪里。这就是为什么PipeConsumer不能很好地适应需要剩余的任务。