2

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?

Roman Cheplyaka
  • 37,738
  • 7
  • 72
  • 121
Jon Nadal
  • 729
  • 9
  • 14
  • Do you really need to connect to the socket in the proxy? Maybe you could just connect to the socket outside of pipes and pass that to your producer to use socketReadS / socketWriteD? – bennofs Jun 23 '13 at 09:14
  • If you'd like, you can take a look at this: https://bitbucket.org/Dwilson1234/haskell-web-server. It might help with your problems. – Dwilson Jun 23 '13 at 12:16

1 Answers1

4

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 ()
Gabriella Gonzalez
  • 34,863
  • 3
  • 77
  • 135
  • Thank you for the suggestion. I updated the code as suggested but am still left with a similar error ("Couldn't match type `p0 a'0 a1 a0 b0 m0` with `PS.SafeIO`" instead of "Couldn't match type `p0 a'0 a1 a0 BS.ByteString m0` with `IO`"). The expected base monad for nsocketReadS and TPS.socketWriteD (SafeIO) does not match. – Jon Nadal Jun 23 '13 at 17:00
  • @JonNadal It's because you need to hoist the `nsocketReadS` and the `socketWriteD` proxies. They expect a `SafeIO` base monad, but you are running them in an environment where their base monad is `Proxy ... SafeIO`. That's what the compiler is complaining about. – Gabriella Gonzalez Jun 24 '13 at 02:39
  • @JonNadal If it makes you feel better, all of this will be much simpler when `pipes-4.0.0` comes out. – Gabriella Gonzalez Jun 24 '13 at 02:39
  • Perfect, thanks! Just read your Google Groups post on `pipes-4.0.0` and I'm looking forward to the simplification. – Jon Nadal Jun 24 '13 at 05:04