How can I get a value after running a conduit?

2019-08-13 23:55发布

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条回答
beautiful°
2楼-- · 2019-08-14 00:27

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.

查看更多
登录 后发表回答