簡體   English   中英

在haskell中將管道組合成一個循環或循環

[英]Composing Pipes into a loop or cycle in haskell

這個問題是關於 Haskell 庫Pipes 的

此問題與 2019 Advent of Code Day 11 (可能劇透警告)有關

我有兩個Pipe Int Int mr brainrobot ,它們也需要在連續循環中相互傳遞信息。 這是輸出brain需要去的輸入robot的輸出和robot需要走的輸入brain brain完成時,我需要計算的結果。

如何將brainrobot成一個循環? 理想情況下,我可以傳遞給runEffect類型為Effect mr的循環

編輯:結果應該是這樣的:

   +-----------+     +-----------+   
   |           |     |           |   
   |           |     |           |   
a ==>    f    ==> b ==>    g    ==> a=|
^  |           |     |           |    |
|  |     |     |     |     |     |    |
|  +-----|-----+     +-----|-----+    |
|        v                 v          |
|        ()                r          |
+=====================================+

答案

最簡單的解決方案是使用 danidiaz 在評論中建議的ClientServer ,因為pipes沒有任何對循環管道的內置支持,如果正確地這樣做的話,這將是非常困難的。 這主要是因為我們需要處理await的數量與yield的數量不匹配的情況。

編輯:我添加了一個關於其他答案問題的部分。 請參閱“另一個有問題的替代方案”部分

編輯 2:我在下面添加了一個問題較少的可能解決方案。 請參閱“可能的解決方案”部分

有問題的替代方案

然而,可以借助Proxy框架(帶有ClientServer )和簡潔的函數generalize來模擬它,它將單向Pipe變成雙向Proxy

                                       generalize f x0
   +-----------+                   +---------------------+
   |           |                   |                     |
   |           |                x <======================== x
a ==>    f    ==> b   becomes      |                     |
   |           |                a ==>         f         ==> b
   |     |     |                   |                     |
   +-----|-----+                   +----------|----------+
         v                                    v     
         r                                    r     

現在我們可以使用//>>\\\\來插入末端並使流循環:

loop :: Monad m => Pipe a a m r -> a -> Effect m r
loop p x0 = pure >\\ generalize p x0 //> pure

有這個形狀

            loop f

              a 
        +-----|-----+
        |     |     |
 /====<=======/===<========\
 |      |           |      |
 \=> a ==>    f    ==> a ==/
        |           |
        +-----|-----+
              v    
              r    

如您所見,我們需要為a輸入初始值。 這是因為無法保證管道在產生之前不會await ,這將迫使它永遠等待。

但是請注意,如果管道在await之前多次yield s,這將丟棄數據,因為 generalize 在內部使用狀態 monad 實現,該狀態 monad 在yield時保存最后一個值並在等待時檢索最后一個值。

用法(有問題的想法)

要將它與您的管道一起使用,只需將它們組合起來並讓它們loop

runEffect $ loop (f >-> g)

但是請不要使用它,因為如果您不小心它會隨機丟棄數據

另一個有問題的選擇

你也可以像mingmingrr建議的那樣制作一個懶惰的無限管道鏈

infiniteChain :: Functor m => Pipe a a m r -> Producer a m r
infiniteChain f = infiniteChain >-> f

這解決了丟棄/重復值的問題,但還有其他幾個問題。 首先,在讓步之前先等待會導致無限循環使用無限內存,但這已經在 mingmingrr 的回答中解決了。

另一個更難解決的問題是,在相應的 yield 之前的每個 action 都會為每個 await 重復一次。 如果我們修改他們的示例以記錄正在發生的事情,我們可以看到這一點:

import Pipes
import qualified Pipes.Prelude as P

f :: Monad m => Pipe Int Int m r
f = P.map (* 2)

g :: Monad m => Int -> Pipe Int Int m ()
g 0 = return ()
g n = do
  lift . putStrLn $ "Awaiting. n = " ++ show n
  x <- await
  lift . putStrLn $ "Got: x = " ++ show x ++ " and n = "++ show n ;
  yield (x + 1)
  g (n - 1)

cyclic' :: Monad m => Int -> Producer Int m Int
cyclic' input = let pipe = (yield input >> pipe) >-> f >-> g 6 in pipe

現在,運行runEffect (cyclic' 0 >-> P.print)將打印以下內容:

Awaiting. n = 6
Got: x = 0 and n = 6
1
Awaiting. n = 5
Awaiting. n = 6
Got: x = 0 and n = 6
Got: x = 2 and n = 5
3
Awaiting. n = 4
Awaiting. n = 5
Awaiting. n = 6
Got: x = 0 and n = 6
Got: x = 2 and n = 5
Got: x = 6 and n = 4
7
Awaiting. n = 3
Awaiting. n = 4
Awaiting. n = 5
Awaiting. n = 6
Got: x = 0 and n = 6
Got: x = 2 and n = 5
Got: x = 6 and n = 4
Got: x = 14 and n = 3
15
Awaiting. n = 2
Awaiting. n = 3
Awaiting. n = 4
Awaiting. n = 5
Awaiting. n = 6
Got: x = 0 and n = 6
Got: x = 2 and n = 5
Got: x = 6 and n = 4
Got: x = 14 and n = 3
Got: x = 30 and n = 2
31
Awaiting. n = 1
Awaiting. n = 2
Awaiting. n = 3
Awaiting. n = 4
Awaiting. n = 5
Awaiting. n = 6
Got: x = 0 and n = 6
Got: x = 2 and n = 5
Got: x = 6 and n = 4
Got: x = 14 and n = 3
Got: x = 30 and n = 2
Got: x = 62 and n = 1
63

如您所見,對於每個await ,我們重新執行所有內容,直到相應的yield 更具體地說,等待觸發管道的新副本運行,直到達到產量。 當我們再次等待時,該副本將再次運行直到下一次產生,如果在此期間觸發await ,它將創建另一個副本並運行它直到第一次產生,依此類推。

這意味着在最好的情況下,我們得到O(n^2)而不是線性性能(並且使用O(n)而不是O(1)內存),因為我們為每個動作重復所有內容。 在最壞的情況下,例如,如果我們正在讀取或寫入文件,我們可能會得到完全錯誤的結果,因為我們正在重復副作用。

一個可能的解決方案

如果您真的必須使用Pipe並且不能使用request / respond代替,並且您確定您的代碼永遠不會await超過(或之前)它yield s(或者在這些情況下有一個很好的默認值),我們可以在我之前的嘗試的基礎上提出一個解決方案,該解決方案至少可以在yield多於await時處理這種情況。

訣竅是在generalize的實現中添加一個緩沖區,這樣多余的值就會被存儲而不是被丟棄。 我們還可以將額外參數保留為緩沖區為空時的默認值。

import Pipes.Lift (evalStateP)
import Control.Monad.Trans.State.Strict (state, modify)
import qualified Data.Sequence

generalize' :: Monad m => Pipe a b m r -> x -> Proxy x a x b m r
generalize' p x0 = evalStateP Seq.empty $ up >\\ hoist lift p //> dn
  where
    up () = do
        x <- lift $ state (takeHeadDef x0)
        request x
    dn a = do
        x <- respond a
        lift $ modify (Seq.|> x)
    takeHeadDef :: a -> Seq.Seq a -> (a, Seq.Seq a)
    takeHeadDef x0 xs = (foldr const x0 xs, Seq.drop 1 xs)

如果我們現在將其插入loop的定義中,我們將解決丟棄多余值的問題(以保留緩沖區的內存成本為代價)。 它還可以防止復制除默認值之外的任何值,並且僅在緩沖區為空時使用默認值。

loop' :: Monad m => a -> Pipe a a m r -> Effect m r
loop' x0 p = pure >\\ generalize' p x0 //> pure

如果我們希望在yield之前await是一個錯誤,我們可以簡單地將error作為我們的默認值: loop' (error "Await without yield") somePipe

TL; 博士

使用Pipes.Core ClientServer 它將解決您的問題,而不會導致大量奇怪的錯誤。

如果這是不可能的,在大多數情況下,我的“可能的解決方案”部分帶有generalize的修改版本應該可以完成這項工作。

您可以通過將管道的輸出連接到輸入來制作循環管道。

cyclic :: Functor m => Producer a m r
cyclic = cyclic >-> f >-> g

考慮以下示例:

import Pipes
import qualified Pipes.Prelude as P

f :: Monad m => Pipe Int Int m r
f = P.map (* 2)

g :: Monad m => Int -> Pipe Int Int m Int
g 0 = return 100
g n = do x <- await ; yield (x + 1) ; g (n - 1)

由於這里的fg在等待之前都不會產生任何輸出,因此使用cyclic = cyclic >-> f >-> g將導致f永遠等待。 避免這種情況的關鍵是確保fg在等待之前產生一些東西,或者像這樣將初始輸入輸入到第一個管道:

cyclic' :: Monad m => Int -> Producer Int m Int
cyclic' input = let pipe = (yield input >> pipe) >-> f >-> g 6 in pipe

這里運行runEffect (cyclic' 0 >-> P.print) return 100並打印1 3 7 15 31 63

PS(可能出現 Code 2019 劇透)您可以使用相同的方案來完成第 7 天。如果您的 Intcode 計算機類型為StateT IntcodeState (Pipe Int Int m) ,那么您可以使用replicate 5 (evalState runIntcode initialIntcodeState)來獲得 5 個管道對應於 5 個放大器中的每一個。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM