Haskell Language
pipes
Zoeken…
Opmerkingen
Zoals de hackpagina beschrijft:
pipes is een schone en krachtige stroomverwerkingsbibliotheek waarmee u herbruikbare streamingcomponenten kunt bouwen en aansluiten
Programma's die via streaming worden geïmplementeerd, kunnen vaak kort en bondig zijn, met eenvoudige, korte functies waarmee u eenvoudig functies kunt in- of uitschakelen met de ondersteuning van het Haskell-type systeem.
await :: Monad m => Consumer' ama
Trekt een waarde stroomopwaarts, waarbij a
ons invoertype is.
yield :: Monad m => a -> Producer' am ()
Produceer een waarde, waarbij a
het uitvoertype is.
Het wordt ten zeerste aanbevolen dat u het ingesloten Pipes.Tutorial
pakket Pipes.Tutorial
, dat een uitstekend overzicht geeft van de kernconcepten van Pipes en hoe Producer
, Consumer
en Effect
inwerken.
producenten
Een Producer
is een monadische actie die waarden kan yield
voor stroomafwaartse consumptie:
type Producer b = Proxy X () () b
yield :: Monad m => a -> Producer a m ()
Bijvoorbeeld:
naturals :: Monad m => Producer Int m ()
naturals = each [1..] -- each is a utility function exported by Pipes
We kunnen natuurlijk ook Producer
hebben die functies van andere waarden zijn:
naturalsUntil :: Monad m => Int -> Producer Int m ()
naturalsUntil n = each [1..n]
Verbruikers
Een Consumer
kan alleen await
waarden van stroomopwaarts.
type Consumer a = Proxy () a () X
await :: Monad m => Consumer a m a
Bijvoorbeeld:
fancyPrint :: MonadIO m => Consumer String m ()
fancyPrint = forever $ do
numStr <- await
liftIO $ putStrLn ("I received: " ++ numStr)
pipes
Pijpen kunnen zowel await
als yield
.
type Pipe a b = Proxy () a () b
Deze Pipe wacht op een Int
en converteert deze naar een String
:
intToStr :: Monad m => Pipe Int String m ()
intToStr = forever $ await >>= (yield . show)
Lopende buizen met runEffect
We gebruiken runEffect
om onze Pipe
:
main :: IO ()
main = do
runEffect $ naturalsUntil 10 >-> intToStr >-> fancyPrint
Merk op dat runEffect
een Effect
vereist, wat een zelfstandige Proxy
zonder invoer of uitvoer:
runEffect :: Monad m => Effect m r -> m r
type Effect = Proxy X () () X
(waarbij X
het lege type is, ook bekend als Void
).
Leidingen aansluiten
Gebruik >->
om Producer
, Consumer
en Pipe
te verbinden om grotere Pipe
functies samen te stellen.
printNaturals :: MonadIO m => Effect m ()
printNaturals = naturalsUntil 10 >-> intToStr >-> fancyPrint
Producer
, Consumer
, Pipe
en Effect
worden allemaal gedefinieerd in termen van het algemene Proxy
. Daarom kan >->
voor verschillende doeleinden worden gebruikt. Typen gedefinieerd door het linkerargument moeten overeenkomen met het type dat wordt gebruikt door het rechterargument:
(>->) :: 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
De proxy-monadetransformator
pipes
kerngegevenstype van pipes
is de Proxy
monadetransformator. Pipe
, Producer
, Consumer
enzovoort worden gedefinieerd in termen van Proxy
.
Aangezien Proxy
een monadetransformator is, hebben definities van Pipe
s de vorm van monadische scripts die await
en waarden yield
, en daarnaast effecten uitvoeren van de basismonade m
.
Pijpen en netwerkcommunicatie combineren
Pipes ondersteunt eenvoudige binaire communicatie tussen een client en een server
In dit voorbeeld:
- een client maakt verbinding en verzendt een
FirstMessage
- de server ontvangt en beantwoordt
DoSomething 0
- de klant ontvangt en beantwoordt
DoNothing
- stap 2 en 3 worden voor onbepaalde tijd herhaald
Het opdrachtgegevenstype dat via het netwerk wordt uitgewisseld:
-- 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
Hier wacht de server op een client om verbinding te maken:
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 ()
De client maakt dus verbinding:
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 ()