Haskell Language
パイプ
サーチ…
備考
ハッキングページに記載されているとおり、
パイプは、再利用可能なストリーミングコンポーネントを構築して接続できる、きれいで強力なストリーム処理ライブラリです。
ストリーミングで実装されたプログラムは、簡潔で短い機能で、ハスケル型システムのバッキングを使って簡単に「スロットイン/アウト」機能を実現することができます。
await :: Monad m => Consumer' ama
上流から値を引き出します。ここでa
は入力型です。
yield :: Monad m => a -> Producer' am ()
値を生成します。ここでa
は出力タイプです。
組み込みのPipes.Tutorial
パッケージを読むことを強くお勧めします。 Pipes.Tutorial
パッケージは、Pipesのコアコンセプトと、 Producer
、 Consumer
、およびEffect
どのように相互作用するかについての優れた概要を提供します。
プロデューサー
Producer
は、川下消費の価値をyield
ことができるモナド的な行動です:
type Producer b = Proxy X () () b
yield :: Monad m => a -> Producer a m ()
例えば:
naturals :: Monad m => Producer Int m ()
naturals = each [1..] -- each is a utility function exported by Pipes
もちろん、他の値の関数でもあるProducer
持つことができます:
naturalsUntil :: Monad m => Int -> Producer Int m ()
naturalsUntil n = each [1..n]
消費者
Consumer
は上流からの値をawait
だけです。
type Consumer a = Proxy () a () X
await :: Monad m => Consumer a m a
例えば:
fancyPrint :: MonadIO m => Consumer String m ()
fancyPrint = forever $ do
numStr <- await
liftIO $ putStrLn ("I received: " ++ numStr)
パイプ
パイプはawait
ことができ、 yield
することもできます。
type Pipe a b = Proxy () a () b
このパイプはInt
を待ち、それをString
変換します:
intToStr :: Monad m => Pipe Int String m ()
intToStr = forever $ await >>= (yield . show)
runEffectでパイプを実行する
私たちは、使用runEffect
私たち実行するためにPipe
:
main :: IO ()
main = do
runEffect $ naturalsUntil 10 >-> intToStr >-> fancyPrint
runEffect
は、入力または出力のない自己完結型Proxy
であるEffect
が必要であることに注意してください。
runEffect :: Monad m => Effect m r -> m r
type Effect = Proxy X () () X
( X
は空の型で、 Void
とも呼ばれます)。
パイプの接続
>->
を使用してProducer
、 Consumer
、およびPipe
を接続し、より大きなPipe
機能を構成します。
printNaturals :: MonadIO m => Effect m ()
printNaturals = naturalsUntil 10 >-> intToStr >-> fancyPrint
Producer
、 Consumer
、 Pipe
、およびEffect
タイプはすべて、一般的なProxy
タイプの観点から定義されています。したがって、 >->
はさまざまな目的で使用できます。左の引数で定義される型は、右の引数で使用される型と一致する必要があります。
(>->) :: Monad m => Producer b m r -> Consumer b m r -> Effect m r
(>->) :: Monad m => Producer b m r -> Pipe b c m r -> Producer c m r
(>->) :: Monad m => Pipe a b m r -> Consumer b m r -> Consumer a m r
(>->) :: Monad m => Pipe a b m r -> Pipe b c m r -> Pipe a c m r
プロキシモナドトランス
pipes
のコアデータ型は、 Proxy
モナドトランスです。 Pipe
、 Producer
、 Consumer
などは、 Proxy
観点から定義されています。
以来、 Proxy
モナド変圧器である、の定義Pipe
Sはモナドスクリプトの形取るawait
及びyield
さらにベースモナドの影響を行う、値m
。
パイプとネットワーク通信の結合
パイプは、クライアントとサーバーの間の単純なバイナリ通信をサポートします
この例では:
- クライアントは
FirstMessage
接続して送信しFirstMessage
- サーバーは受信し、
DoSomething 0
応答しDoSomething 0
- クライアントは
DoNothing
を受信して応答しますDoNothing
- ステップ2および3が無期限に繰り返される
ネットワークを介して交換されるコマンドデータタイプ:
-- Command.hs
{-# LANGUAGE DeriveGeneric #-}
module Command where
import Data.Binary
import GHC.Generics (Generic)
data Command = FirstMessage
| DoNothing
| DoSomething Int
deriving (Show,Generic)
instance Binary Command
ここで、サーバーはクライアントが接続するのを待ちます。
module Server where
import Pipes
import qualified Pipes.Binary as PipesBinary
import qualified Pipes.Network.TCP as PNT
import qualified Command as C
import qualified Pipes.Parse as PP
import qualified Pipes.Prelude as PipesPrelude
pageSize :: Int
pageSize = 4096
-- pure handler, to be used with PipesPrelude.map
pureHandler :: C.Command -> C.Command
pureHandler c = c -- answers the same command that we have receveid
-- impure handler, to be used with PipesPremude.mapM
sideffectHandler :: MonadIO m => C.Command -> m C.Command
sideffectHandler c = do
liftIO $ putStrLn $ "received message = " ++ (show c)
return $ C.DoSomething 0
-- whatever incoming command `c` from the client, answer DoSomething 0
main :: IO ()
main = PNT.serve (PNT.Host "127.0.0.1") "23456" $
\(connectionSocket, remoteAddress) -> do
putStrLn $ "Remote connection from ip = " ++ (show remoteAddress)
_ <- runEffect $ do
let bytesReceiver = PNT.fromSocket connectionSocket pageSize
let commandDecoder = PP.parsed PipesBinary.decode bytesReceiver
commandDecoder >-> PipesPrelude.mapM sideffectHandler >-> for cat PipesBinary.encode >-> PNT.toSocket connectionSocket
-- if we want to use the pureHandler
--commandDecoder >-> PipesPrelude.map pureHandler >-> for cat PipesBinary.Encode >-> PNT.toSocket connectionSocket
return ()
クライアントはこうして接続します:
module Client where
import Pipes
import qualified Pipes.Binary as PipesBinary
import qualified Pipes.Network.TCP as PNT
import qualified Pipes.Prelude as PipesPrelude
import qualified Pipes.Parse as PP
import qualified Command as C
pageSize :: Int
pageSize = 4096
-- pure handler, to be used with PipesPrelude.amp
pureHandler :: C.Command -> C.Command
pureHandler c = c -- answer the same command received from the server
-- inpure handler, to be used with PipesPremude.mapM
sideffectHandler :: MonadIO m => C.Command -> m C.Command
sideffectHandler c = do
liftIO $ putStrLn $ "Received: " ++ (show c)
return C.DoNothing -- whatever is received from server, answer DoNothing
main :: IO ()
main = PNT.connect ("127.0.0.1") "23456" $
\(connectionSocket, remoteAddress) -> do
putStrLn $ "Connected to distant server ip = " ++ (show remoteAddress)
sendFirstMessage connectionSocket
_ <- runEffect $ do
let bytesReceiver = PNT.fromSocket connectionSocket pageSize
let commandDecoder = PP.parsed PipesBinary.decode bytesReceiver
commandDecoder >-> PipesPrelude.mapM sideffectHandler >-> for cat PipesBinary.encode >-> PNT.toSocket connectionSocket
return ()
sendFirstMessage :: PNT.Socket -> IO ()
sendFirstMessage s = do
_ <- runEffect $ do
let encodedProducer = PipesBinary.encode C.FirstMessage
encodedProducer >-> PNT.toSocket s
return ()