How can I get a value after running a conduit?

2019-08-14 00:17发布

问题:

I need to do a little back and forth between with client and get either the Client object or their name string before starting up more pipelines.

But I can't seem to get appSink to let me have a return value.

How should I do this?

checkAddClient :: Server -> ClientName -> AppData -> IO (Maybe Client)
checkAddClient server@Server{..} name app = atomically $ do
  clientmap <- readTVar clients
  if Map.member name clientmap
    then return Nothing
    else do
        client <- newClient name app
        writeTVar clients $ Map.insert name client clientmap
        return (Just client)


readName server app = go
  where
  go = do
    yield "What is your name? "
    name <- lineAsciiC $ takeCE 80 =$= filterCE (/= _cr) =$= foldC
    if BS.null name
      then go
      else do
        ok <- liftIO $ checkAddClient server name app
        case ok of
            Nothing -> do
                yield . BS.pack $ printf "The name '%s' is in use, please choose another\n" $ BS.unpack name
                go
            Just client -> do
                yield . BS.pack $ printf "Welcome, %s!\n" $ BS.unpack name
                return client -- <-- Here is the problem!!

main :: IO ()
main = do
    server <- newServer
    runTCPServer (serverSettings 4000 "*") $ \clientApp -> do
        (clientC, client) <- appSource clientApp $$+ readName server clientApp =$ appSink clientApp

UPDATE

Here is the solution I ended up with:

readName :: Server -> AppData -> Sink BS.ByteString IO Client
readName server app = go
  where
  go = do
    yield "What is your name? " $$ appSink app
    name <- lineAsciiC $ takeCE 80 =$= filterCE (/= _cr) =$= foldC
    if BS.null name
      then go
      else do
        ok <- liftIO $ checkAddClient server name app
        case ok of
            Nothing -> do
                yield (BS.pack $ printf "The name '%s' is in use, please choose another\n" $ BS.unpack name) $$ appSink app
                go
            Just client -> do
                yield (BS.pack $ printf "Welcome, %s!\n" $ BS.unpack name) $$ appSink app
                return client


main :: IO ()
main = do
    server <- newServer
    runTCPServer (serverSettings 4000 "*") $ \clientC -> do
        client <- appSource clientC $$ readName server clientC
        print $ clientName client

回答1:

This is a limitation of the primary conduit API: you're not able to get the result value from anything but the most downstream component. There are a few workarounds:

  1. There's a more advanced conduit API which does allow capturing upstream finalizers. The function you'd be interested in is withUpstream. Note that this is the "right" approach to the problem, but there's a reason this more advanced API isn't the primary one: it has six type parameters, and tends to confuse people.

  2. Instead of fusing readName to appSink, pass appSink into readName and fuse it at each yield call. E.g.:

    yield (BS.pack $ printf "...") $$ appSink app
    

    This is probably the best balance between simplicity and type safety.

  3. Create an IORef or other mutable variable, and put the client's name into that mutable variable.