Sto implementando un bot IRC e dal momento che mi sto collegando su SSL utilizzando OpenSSL.Session, utilizzo la funzione lazyRead
per leggere i dati dal socket. Durante la fase iniziale della connessione ho bisogno di eseguire diverse cose in ordine: negoziazione del nick, identificazione del nickserver, collegamento ai canali ecc. Quindi c'è qualche stato coinvolto. In questo momento mi si avvicinò con il seguente:Che cos'è un modo idiomatico di gestire un canale di input pigro in Haskell
data ConnectionState = Initial | NickIdentification | Connected
listen :: SSL.SSL -> IO()
listen ssl = do
lines <- BL.lines `fmap` SSL.lazyRead ssl
evalStateT (mapM_ (processLine ssl) lines) Initial
processLine :: SSL.SSL -> BL.ByteString -> StateT ConnectionState IO()
processLine ssl line = do case message of
Just a -> processMessage ssl a
Nothing -> return()
where message = IRC.decode $ BL.toStrict line
processMessage :: SSL.SSL -> IRC.Message -> StateT ConnectionState IO()
processMessage ssl m = do
state <- S.get
case state of
Initial -> when (IRC.msg_command m == "376") $ do
liftIO $ putStrLn "connected!"
liftIO $ privmsg ssl "NickServ" ("identify " ++ nick_password)
S.put NickIdentification
NickIdentification -> do
when (identified m) $ do
liftIO $ putStrLn "identified!"
liftIO $ joinChannel ssl chan
S.put Connected
Connected -> return()
liftIO $ print m
when (IRC.msg_command m == "PING") $ (liftIO . pong . mconcat . map show) (IRC.msg_params m)
Così quando arrivo allo stato "Connected" Ho ancora finiscono per passare attraverso l'istruzione case anche se è veramente solo bisogno di inizializzare la connessione. L'altro problema è che aggiungere StateT annidati sarebbe molto doloroso.
Un altro modo sarebbe quello di sostituire mapM
con qualcosa di personalizzato per elaborare solo le linee fino a quando non siamo connessi e quindi avviare un altro ciclo sul resto. Ciò richiederebbe di tenere traccia di ciò che è rimasto nella lista o invocando ancora una volta SSL.lazyRead
(che non è male).
Un'altra soluzione consiste nel mantenere l'elenco di righe rimanenti nello stato e disegnare le righe quando necessario in modo simile a getLine
.
Qual è la cosa migliore da fare in questo caso? La pigrizia di Haskell è tale da andare direttamente al caso Connected
dopo l'interruzione dello stato o case
è sempre rigoroso?
Un'altra alternativa sarebbe utilizzare [conduit] (https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/conduit-overview). –