我正在尝试使用管道和基于它构建的各种库来编写基本网络服务器。预期的流程是:
从socket获取bytestring - >使用二进制解码 - >服务器逻辑在这里 - >发送响应socket
我认为这将是:
fromSocket s 4096 >-> decode >-> serverLogic >-> toSocket s
pipes-binary有一个decode
和一个decodeMany
,但我不确定我是否理解其中的差异,而且我不知道如何使用decode
。为什么decodeMany
将上游管道作为参数而不是使用>->
将其链接起来?你如何使用decode
,StateT
是什么,我的管道最终应该是什么样的?
答案 0 :(得分:5)
StateT (Producer a m r) m x
成语来自pipes-parse
's "Low-level Parsers"。这通常意味着库正在使用draw
和unDraw
从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
和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)
我们首先看到decode
drawing
ByteString
来自Producer ByteString
的{{1}}个状态,以便尝试解析b
。由于ByteString
上的块边界可能与解析边界不对齐,因此在StateT
中执行此操作非常重要,这样剩余的块可以unDraw
- 重新导入{{1} }}
Producer
在decodeMany
之上构建并尝试重复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”思考的原因是因为否则剩余的概念没有明确定义。如果你没有清楚地定义你的来源是什么,你就无法清楚地说明剩余价值应该去哪里。这就是为什么Pipe
和Consumer
不能很好地适应需要剩余的任务。