Haskell 管道3.0:非线性拓扑
我正在看一看用于流处理的pipes 3.0包。做得很好,也很清楚,只是我不能把我的头放在“压缩和合并”部分 我的目标是组合管道,有点像ArrowChoice允许的那样:Haskell 管道3.0:非线性拓扑,haskell,haskell-pipes,Haskell,Haskell Pipes,我正在看一看用于流处理的pipes 3.0包。做得很好,也很清楚,只是我不能把我的头放在“压缩和合并”部分 我的目标是组合管道,有点像ArrowChoice允许的那样: 我有一个独一无二的制作人 我想将第一个管道应用于左值,另一个应用于右值 然后我想合并结果,并继续 我在教程中定义了fork: fork () = runIdentityP . hoist (runIdentityP . hoist runIdentityP) $ forever $ do a <
- 我有一个独一无二的制作人
- 我想将第一个管道应用于左值,另一个应用于右值
- 然后我想合并结果,并继续
fork
:
fork () =
runIdentityP . hoist (runIdentityP . hoist runIdentityP) $ forever $ do
a <- request ()
lift $ respond a
lift $ lift $ respond a
oddOrEven x = if odd x then Left x else Right x
producer = fromListS [1..0] >-> mapD oddOrEven
isLeft (Left _) = True
isLeft (Right _) = False
isRight = not . isLeft
filterLeft = filterD isLeft
filterRight = filterD isRight
pipe1 = mapD (\x -> ("seen on left", x))
pipe2 = mapD (\x -> ("seen on right", x))
p1 = producer >-> fork
fork()=
runIdentityP。起重机(运行标识YP.起重机运行标识YP)$forever$do
a->mapD oddOrEven
isLeft(左)=真
isLeft(右)=False
isRight=不是。孤岛
filterLeft=filterD isLeft
filterRight=filterD isRight
pipe1=mapD(\x->(“见左”,x))
pipe2=mapD(\x->(“见右”,x))
p1=生产商>->fork
问题是我不能正确地输入。本教程似乎只演示如何将内部(提升的)管道链作为一个自包含的会话运行,但我希望能够将其值重新导入管道,而不仅仅是对其应用效果。我当然试着模仿这些类型,但它们很快就会有点毛茸茸的
有人能帮我吗?提前谢谢
(注:这种拓扑的一个例子将是本教程的一个很好的补充,或者更好地作为一个关于如何使用管道模拟
控件.Arrow
的章节)管道抽象不支持菱形拓扑或任何形式的Arrow
类行为。这不是API问题,而是此类场景没有正确或定义良好的行为
为了解释原因,请允许我将您的图表简化为以下图表:
+----+
| pL |
+----+ => +----+ => +----+
| p1 | | p2 |
+----+ => +----+ => +----+
| pR |
+----+
假设我们在p1
管道上,我们响应pL
。如果您还记得本教程,代理法则要求每个respond
阻塞直到上游。这意味着在再次请求之前,p1
无法重新获得控制权。因此,在这一点上,我们有:
p1
在等待来自pL的请求时被阻止
但是,假设pL
还没有请求
,而是用它自己的值对p2
响应
s。现在我们有:
p1
在等待来自pL的请求时被阻止
pL
在等待来自p2的请求时被阻止
现在假设p2
而不是request
s frompR
。代理法规定,p2
在pR
再次响应之前无法重新获得控制权。现在我们有:
p1
在等待来自pL的请求时被阻止
pL
在等待来自p2的请求时被阻止
p2
被阻止,等待响应来自pR
现在,当pR
请求s来自p1
的值时会发生什么?如果我们查看块列表,p1
仍然被阻止,等待来自pL
的请求,因此无法接收来自pR
的请求。可以说,即使pL
和pR
共享了相同的请求
签名,也没有正确的“打结”方法
更一般地说,代理法律确保以下两个不变量:
- 活动管道的每个“上游”管道将在
respond
- acive管道的每个“下游”管道将在
请求时被阻塞
循环或菱形会破坏这些不变量。这就是为什么本教程非常简短地附带指出循环拓扑没有“意义”
你可以在我刚才给你的例子中看到为什么钻石会打破这个不变量。当p1
拥有控制权时,它位于pR
的上游,这意味着pR
在请求中被阻止。然而,当p2
获得控制时,它位于pR
的下游,这意味着pR
在respond
上被阻断。这导致了一个矛盾,因为自从控制通过pL
而不是pR
到达p2
以来,pR
不可能改变
机器
所以有两种方法可以解决你的问题。一种解决方案是将所需的拆分行为内联到单个管道中。您可以定义一个pE
管道,该管道将pL
和pR
的行为组合到一个管道中
这个问题的更优雅的解决方案是爱德华的机器的风格。您定义了一个比支持arrowthoice
的代理功能更为有限的抽象,您在该抽象的域中执行arrow-ish操作,然后在完成后将其升级为代理
如果您眯着眼睛看,您可以假装Haskell中存在一类当前可用的协同程序抽象,这是一种偏序。协同程序抽象是对象,从协同程序抽象C1
到协同程序抽象C2
的箭头表示可以将C1
类型的协同程序嵌入C2
类型的协同程序中(即C1
是C2
的不适当子集)
按照这种偏序,代理可能是终端对象,这意味着您可以将代理视为协同程序的汇编语言。与汇编语言类似,代理提供的保证较少,但您可以在代理中嵌入限制性更强的协程抽象(即更高级的语言)。这些高级语言提供
+----+
| pL |
+----+ => +----+ => +----+
| p1 | | p2 |
+----+ => +----+ => +----+
| pR |
+----+
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Category (Kleisli m) where
id = Kleisli return
(Kleisli f) . (Kleisli g) = Kleisli (f <=< g)
kleisliToProxy :: (Proxy p) => Kleisli m a b -> () -> Pipe p a b m r
kleisliToProxy (Kleisli f) = mapMD f
kleisliToProxy id = idT
kleisliToProxy (f . g) = kleisliToProxy f <-< kleisliToProxy g