これには非常に単純で一般的な解決策があります。重要なアイデアは、異なるタイプのソースを決してマージしないことです。代わりに、同じタイプのソースのみをマージします。これを機能させる秘訣は、すべての多様なソースの出力を代数的データ型でラップすることです。
私はあまり詳しくないnetwire
ので、よろしければpipes
例として使用します。必要なのは、merge
ソースのリストを取得し、それらを1つのソースに結合して、それらの出力を同時にマージし、すべてが完了すると終了する関数です。キータイプの署名は次のとおりです。
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()
Producer
これは、タイプの値のリストを取得し、それらをタイプの値のa
1つに結合することを意味します。興味があり、フォローしたい場合は、の実装を次に示します。Producer
a
merge
import Control.Concurrent
import Control.Concurrent.Chan
import Control.Monad
import Control.Proxy
fromNChan :: (Proxy p) => Int -> Chan (Maybe a) -> () -> Producer p a IO ()
fromNChan n0 chan () = runIdentityP $ loop n0 where
loop 0 = return ()
loop n = do
ma <- lift $ readChan chan
case ma of
Nothing -> loop (n - 1)
Just a -> do
respond a
loop n
toChan :: (Proxy p) => Chan ma -> () -> Consumer p ma IO r
toChan chan () = runIdentityP $ forever $ do
ma <- request ()
lift $ writeChan chan ma
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()
merge producers () = runIdentityP $ do
chan <- lift newChan
lift $ forM_ producers $ \producer -> do
let producer' () = do
(producer >-> mapD Just) ()
respond Nothing
forkIO $ runProxy $ producer' >-> toChan chan
fromNChan (length producers) chan ()
ここで、2つの入力ソースがあると想像してみましょう。最初のものは、1秒間隔でから1
までの整数を生成します。10
throttle :: (Proxy p) => Int -> () -> Pipe p a a IO r
throttle microseconds () = runIdentityP $ forever $ do
a <- request ()
respond a
lift $ threadDelay microseconds
source1 :: (Proxy p) => () -> Producer p Int IO ()
source1 = enumFromS 1 10 >-> throttle 1000000
String
2番目のソースは、ユーザー入力から3秒を読み取ります。
source2 :: (Proxy p) => () -> Producer p String IO ()
source2 = getLineS >-> takeB_ 3
これら2つのソースを結合したいのですが、それらの出力タイプが一致しないため、代数的データ型を定義して、それらの出力を単一のタイプに統合します。
data Merge = UserInput String | AutoInt Int deriving Show
これで、それらの出力を代数的データ型でラップすることにより、それらを同じ型のプロデューサーの単一のリストに組み合わせることができます。
producers :: (Proxy p) => [() -> Producer p Merge IO ()]
producers =
[ source1 >-> mapD UserInput
, source2 >-> mapD AutoInt
]
そして、私たちはそれを本当に素早くテストすることができます:
>>> runProxy $ merge producers >-> printD
AutoInt 1
Test<Enter>
UserInput "Test"
AutoInt 2
AutoInt 3
AutoInt 4
AutoInt 5
Apple<Enter>
UserInput "Apple"
AutoInt 6
AutoInt 7
AutoInt 8
AutoInt 9
AutoInt 10
Banana<Enter>
UserInput "Banana"
>>>
これで、結合されたソースができました。次に、ゲームエンジンを作成して、そのソースから読み取り、入力のパターンマッチを行い、適切に動作するようにします。
engine :: (Proxy p) => () -> Consumer p Merge IO ()
engine () = runIdentityP loop where
loop = do
m <- request ()
case m of
AutoInt n -> do
lift $ putStrLn $ "Generate unit wave #" ++ show n
loop
UserInput str -> case str of
"quit" -> return ()
_ -> loop
試してみよう:
>>> runProxy $ merge producers >-> engine
Generate unit wave #1
Generate unit wave #2
Generate unit wave #3
Test<Enter>
Generate unit wave #4
quit<Enter>
>>>
同じトリックがうまくいくと思いnetwire
ます。