Haskell Language
Tubería
Buscar..
Observaciones
Como la página de hackage describe:
pipe es una biblioteca de procesamiento de secuencias limpia y potente que le permite crear y conectar componentes de transmisión reutilizables
Los programas implementados a través de la transmisión a menudo pueden ser sucintos y compositivos, con funciones cortas y simples que le permiten "encajar o sacar" fácilmente las funciones con el respaldo del sistema de tipo Haskell.
await :: Monad m => Consumer' ama
Extrae un valor del flujo ascendente, donde a
es nuestro tipo de entrada.
yield :: Monad m => a -> Producer' am ()
Produce un valor, donde a
es el tipo de salida.
Es muy recomendable que lea el paquete de Pipes.Tutorial
incorporado que ofrece una excelente visión general de los conceptos básicos de Pipes y cómo interactúan Producer
, Consumer
y Effect
.
Productores
Un Producer
es una acción monádica que puede yield
valores para el consumo posterior:
type Producer b = Proxy X () () b
yield :: Monad m => a -> Producer a m ()
Por ejemplo:
naturals :: Monad m => Producer Int m ()
naturals = each [1..] -- each is a utility function exported by Pipes
Por supuesto, podemos tener Producer
que son funciones de otros valores también:
naturalsUntil :: Monad m => Int -> Producer Int m ()
naturalsUntil n = each [1..n]
Los consumidores
Un Consumer
solo puede await
valores de upstream.
type Consumer a = Proxy () a () X
await :: Monad m => Consumer a m a
Por ejemplo:
fancyPrint :: MonadIO m => Consumer String m ()
fancyPrint = forever $ do
numStr <- await
liftIO $ putStrLn ("I received: " ++ numStr)
Tubería
Las tuberías pueden await
y yield
.
type Pipe a b = Proxy () a () b
Este Pipe espera un Int
y lo convierte en una String
:
intToStr :: Monad m => Pipe Int String m ()
intToStr = forever $ await >>= (yield . show)
Corriendo tuberías con runEffect
Usamos runEffect
para ejecutar nuestro Pipe
:
main :: IO ()
main = do
runEffect $ naturalsUntil 10 >-> intToStr >-> fancyPrint
Tenga en cuenta que runEffect
requiere un Effect
, que es un Proxy
autónomo sin entradas ni salidas:
runEffect :: Monad m => Effect m r -> m r
type Effect = Proxy X () () X
(donde X
es el tipo vacío, también conocido como Void
).
Tubos de conexión
Use >->
para conectar Producer
s, Consumer
s y Pipe
s para componer funciones de Pipe
más grandes.
printNaturals :: MonadIO m => Effect m ()
printNaturals = naturalsUntil 10 >-> intToStr >-> fancyPrint
Consumer
tipos Producer
, Consumer
, Pipe
y Effect
se definen en términos del tipo de Proxy
general. Por lo tanto, >->
se puede utilizar para una variedad de propósitos. Los tipos definidos por el argumento de la izquierda deben coincidir con el tipo consumido por el argumento de la derecha:
(>->) :: 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
El transformador proxy mónada
El tipo de datos central de las pipes
es el transformador de mónada Proxy
. Pipe
, Producer
, Consumer
, etc. se definen en términos de Proxy
.
Como Proxy
es un transformador de mónadas, las definiciones de Pipe
s toman la forma de scripts monádicos que await
y yield
valores, además de realizar efectos desde la mónada base m
.
Combinación de tuberías y comunicación en red.
Pipes admite la comunicación binaria simple entre un cliente y un servidor
En este ejemplo:
- un cliente se conecta y envía un
FirstMessage
- El servidor recibe y responde
DoSomething 0
- El cliente recibe y responde
DoNothing
- Los pasos 2 y 3 se repiten indefinidamente.
El tipo de datos de comando intercambiado a través de la red:
-- 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
Aquí, el servidor espera a que un cliente se conecte:
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 ()
El cliente se conecta así:
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 ()