Как я могу получить значение после запуска канала?

Мне нужно немного походить туда-сюда между клиентом и получить либо объект Client, либо его строку имени, прежде чем запускать больше конвейеров.

Но я не могу заставить appSink дать мне возвращаемое значение.

Как мне это сделать?

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

ОБНОВЛЕНИЕ

Вот решение, которое у меня получилось:

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

person Joe Hillenbrand    schedule 28.05.2014    source источник


Ответы (1)


Это ограничение основного API канала: вы не можете получить значение результата ни от чего, кроме самого нижестоящего компонента. Есть несколько обходных путей:

  1. Существует более продвинутый API канала, который действительно позволяет захватывать финализаторы восходящего потока. Вам будет интересна функция с восходящим потоком. Обратите внимание, что это «правильный» подход к проблеме, но есть причина, по которой этот более продвинутый API не является основным: он имеет шесть параметров типа и имеет тенденцию сбивать людей с толку.

  2. Вместо объединения readName с appSink передайте appSink в readName и объединяйте его при каждом вызове yield. Например.:

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

    Это, вероятно, лучший баланс между простотой и безопасностью типов.

  3. Создайте IORef или другую изменяемую переменную и поместите имя клиента в эту изменяемую переменную.

person Michael Snoyman    schedule 29.05.2014
comment
Я пошел по варианту №2. Работает отлично! Спасибо за вашу помощь. - person Joe Hillenbrand; 29.05.2014