How can I conditionally apply a conduit?
Question
I have a Conduit of type Conduit a m a
and a function of type (a -> Maybe a)
. I want to run the function, and then if it returns Nothing, use the Conduit. That is, I want a function of type
maybePipe :: Conduit a m b -> (a -> Maybe b) -> Conduit a m b
or, of the more restricted type
maybePipe :: Conduit a m a -> (a -> Maybe a) -> Conduit a m a
If it helps, my specific case is as follows:
I'm writing code that deals with IRC messages, and I have a function:
runClient :: Conduit IRC.Message IO IRC.Message -> ClientSettings -> IO ()
runClient pipe address = runTCPClient' pipe' address where
pipe' = mapC IRC.decode $= concatMapC id $= pipe $= mapC IRC.encode $= mapC (++ "\r\n")
handlePings (IRC.Message (Just (IRC.Server serverName)) "PING" []) = Just $ IRC.pong serverName
handlePings (IRC.Message Nothing "PING" [server]) = Just $ IRC.pong server
handlePings (IRC.Message Nothing "PING" []) = Just $ IRC.pong (getHost address)
handlePings _ = Nothing
runTCPClient' :: Conduit ByteString IO ByteString -> ClientSettings -> IO ()
runTCPClient' pipe address = runTCPClient address runClient where
runClient appdata = appSource appdata $= linesUnboundedAsciiC $= pipe $$ appSink appdata
I want to be able to do maybePipe handlePings pipe
(or equivalent) in that function, so when the IRC message is a ping, we respond with a pong and don't call the user-specified Conduit.
Solution 2
Using Michael's combinator only calls the (a -> Maybe b)
on the first item it comes acress, then lets the onNothing
pipe take over. This was not what I was looking for.
Instead, using ZipConduit
, in my specific example (using conduit-combinators
):
pingHandlingPipe =
getZipConduit $ ZipConduit (concatMapC handlePings)
*> ZipConduit (takeWhileC (not.isJust.handlePings) $= pipe)
or, generalized
pipeMaybe maybeF pipe =
getZipConduit $ ZipConduit (concatMapC maybeF)
*> ZipConduit (takeWhileC (not.isJust.maybeF) $= pipe)
Unfortunately, this calls the (a -> Maybe b)
function two times.
OTHER TIPS
Searching Hoogle reveals a function with almost exactly that type signature: mapOutputMaybe. But the more idiomatic way would be to fuse with Data.Conduit.List.mapMaybe
.
EDIT
Scratch that, I understand what you're asking now. No, there's no built in combinator. But it's easy to build one up:
myHelper onNothing f = awaitForever $ maybe onNothing yield . f