8

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

从套接字获取字节串 -> 使用二进制解码 -> 服务器逻辑在这里 -> 向套接字发送响应

我认为会是这样的:

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

管道二进制有 adecode和 a decodeMany,但我不确定我是否理解其中的区别,也不知道如何使用decode. 为什么decodeMany将上游管道作为参数而不是与它挂钩>->?你如何使用decodeStateTfor 是什么,我的管道链最终应该是什么样子?

4

2 回答 2

5

StateT (Producer a m r) m x成语来自pipes-parse's "Low-level Parsers"。这通常意味着库正在使用drawunDraw从 a 中提取值Producer并在它们未使用时返回它们。它是解析可能发生故障的重要组成部分。它还要求该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 ()

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

-- 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)

我们首先看到decodedrawing足够的ByteString来自有Producer ByteString状态的块,以便尝试解析b. 由于ByteStrings 上的块边界可能与解析边界不对齐,因此这样做很重要,StateT以便剩余的块可以unDraw-ed 回到Producer.

decodeMany构建在顶部decode并尝试重复关闭输入 Producer在失败时返回剩余sdecode b的“延续” 。ProducerByteString

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

于 2014-02-01T22:46:08.277 回答
3

我想通过回答您关于为什么解码器不是Pipe.

Pipea与以下类型的区别:

pipe :: Pipe a b m r

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

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

...是aPipe可以用来变换Producers、Consumers和其他Pipes:

(>-> 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”只能转换Producers。有些东西不能用Pipes 正确建模,剩下的就是其中之一。

conduit声称使用Conduits(s 的conduit类似物Pipe)对剩菜进行建模,但它弄错了。我整理了一个简单的例子来说明原因。首先,只需实现一个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)

...但是如果您在 a 的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 并添加了一个广泛的教程,您可以在此处阅读。

这个新的 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“根据 s 思考Producer”,是因为否则剩余的概念没有明确定义。如果你没有清楚地定义你的来源是什么,你就不能清楚地阐明剩余价值应该去哪里。这就是为什么Pipes 和Consumers 不适合需要剩菜的任务。

于 2014-02-02T11:51:29.613 回答