Refactoring a Telnet parser I'm writing a Telnet parser in Haskell that can run within any monad that implements some event-handling functions. When it finishes processing a block of input - whether because it read it all or because it was interrupted by an event handler - it returns the remainder of the text and a continuation for more input.

Right now, the code works, but it's pretty ugly. I've tried to make the patterns I saw in the program as obvious as possible, but I'm stymied at the actual refactoring stage.

I'm still pretty new to Haskell, so any feedback and advice on best practices is appreciated!

import qualified Data.ByteString as BS
import           Data.ByteString (ByteString)
import Data.Word (Word8)
import Control.Monad


{-
 - I suspect I can change these datatypes to use something like
 -   data Parser m r = Parser (ByteString -&gt; m r)
 - but I'm not sure how to go about doing that.
 -}
data TelnetParser m = TelnetParser (ByteString -&gt; m (TelnetState m))
type TelnetState m = (ByteString, TelnetParser m)

{-
 - These are the handler functions that the parser requires.
 - These allow you to execute other monadic actions in response to
 - Telnet events.
 -}
class (Monad m) =&gt; Telnet m where
  onText    :: Word8 -&gt;          m ()
  onCommand :: Word8 -&gt;          m Bool
  onOption  :: Word8 -&gt; Word8 -&gt; m Bool

  onText _     = return ()
  onCommand _  = return True
  onOption _ _ = return True


readTelnet :: (Telnet m) =&gt; ByteString -&gt; TelnetParser m -&gt; m (TelnetState m)
readTelnet str (TelnetParser f)
  | BS.null str = return (str, TelnetParser f)
  | otherwise   = f str

isOption :: Word8 -&gt; Bool
isOption = (`elem` [250..254])

decideContinue :: (Telnet m) =&gt; Bool -&gt; ByteString -&gt; TelnetParser m -&gt; m (TelnetState m)
decideContinue True = readTelnet
decideContinue False = curry return


{-
 - These functions implement the various states of the Telnet parser.
 -}

readOption :: (Telnet m) =&gt; Word8 -&gt; ByteString -&gt; m (TelnetState m)
readOption cmd str = do
    action &lt;- decideContinue `liftM` onOption cmd ch
    action rest (TelnetParser readText)
  where (ch, rest) = (BS.head str, BS.tail str)

readIAC :: (Telnet m) =&gt; ByteString -&gt; m (TelnetState m)
readIAC str
    | ch == 255    = onText ch &gt;&gt; (rest `readTelnet` TelnetParser readText)
    | isOption ch  =              (rest `readTelnet` TelnetParser (readOption ch))
    | otherwise    = do
        action &lt;- decideContinue `liftM` onCommand ch
        action rest (TelnetParser readText)
  where (ch, rest) = (BS.head str, BS.tail str)

readCR :: (Telnet m) =&gt; ByteString -&gt; m (TelnetState m)
readCR str
    | ch == 10    = onText 13 &gt;&gt; onText 10 &gt;&gt; (rest `readTelnet` TelnetParser readText)
    | ch ==  0    = onText 13 &gt;&gt;              (rest `readTelnet` TelnetParser readText)
    | otherwise   = onText 13 &gt;&gt;              (rest `readTelnet` TelnetParser readText)
  where (ch, rest) = (BS.head str, BS.tail str)

readText :: (Telnet m) =&gt; ByteString -&gt; m (TelnetState m)
readText str
    | ch == 255   =              (rest `readTelnet` TelnetParser readIAC)
    | ch == 13    =              (rest `readTelnet` TelnetParser readCR)
    | otherwise   = onText ch &gt;&gt; (rest `readTelnet` TelnetParser readText)
  where (ch, rest) = (BS.head str, BS.tail str)
I think one of the reason why functional programming is usually easier is its natural modularity. Consider to design your solution in a more layered way. I.e. separate parser and invocations. Avoid callbacks in favor of lazy stream of events (see unsafeInterleaveIO) or borrow ideas from reactive frameworks. Also see Data.Binary.Get module or parsec package for implementing parser.