Search code examples
haskellhaskell-pipes

Haskell Control.Proxy.TCP Producer


I've been playing with the Control.Proxy.TCP library and would like to to create a Producer from a network source.

producer :: Proxy p => HostName -> ServiceName -> () -> Producer p BS.ByteString IO ()
producer h p () = runIdentityP $
    lift $ connect h p $ \(s, r) ->
        runProxy $ nsocketReadS s >-> pxy >-> socketWriteD s
    where
        pxy () = runIdentityP $ do
            respond "resource-id" -- ask for "resource-id"
            bs <- request 1024    -- fetch up to 1024 bytes
            lift $ respond bs     -- and produce them from the outer proxy
            return ()

The code above does not type check:

Couldn't match type `p0 a'0 a1 a0 BS.ByteString m0' with `IO'
Expected type: ()
               -> ProxyFast Int BS.ByteString () BS.ByteString IO ()
  Actual type: ()
               -> ProxyFast
                    Int
                    BS.ByteString
                    ()
                    BS.ByteString
                    (p0 a'0 a1 a0 BS.ByteString m0)
                    ()
In the second argument of `(>->)', namely `pxy'
In the first argument of `(>->)', namely `nsocketReadS s >-> pxy'
In the second argument of `($)', namely
  `nsocketReadS s >-> pxy >-> socketWriteD s'

I see that the base monad for nsocketReadS and socketWriteD is IO while I need a different type. How can I correct this problem?


Solution

  • If you want to allocate a socket within a pipeline, you need to use the Control.Proxy.TCP.Safe module, which has the alternative version of connect that you are looking for:

    connect
      :: (Proxy p, Monad m)
      => (forall x. SafeIO x -> m x)
      -> HostName
      -> ServiceName
      -> ((Socket, SockAddr) -> ExceptionP p a' a b' b m r)
      -> ExceptionP p a' a b' b m r
    

    This uses pipes-safe to manage resource allocation within the pipeline. If you haven't used pipes-safe before then the best place to begin is the pipes-safe tutorial.

    Edit: Update to answer your question in the comment. You need to hoist the socket reader and writer because their base monad is the surrounding proxy, not SafeIO.

    producer
        :: (Proxy p)
        => HostName -> ServiceName
        -> () -> Producer (ExceptionP p) BS.ByteString SafeIO ()
    producer h p () = connect id h p $ \(s, r) ->
        runProxy $ hoist lift . nsocketReadS s >-> pxy >-> hoist lift . socketWriteD s
      where
        pxy () = do
            respond "resource-id" -- ask for "resource-id"
            bs <- request 1024    -- fetch up to 1024 bytes
            lift $ respond bs     -- and produce them from the outer proxy
            return ()