IMAP specs and typing

Submitted by jmuk on Thu, 06/08/2006 - 2:13am.

IMAP, Internet Message Access Protocol, is a fairly complecated protocol (comparing with POP3). Its specification can be read as RFC 3501.

I worry about the typing of IMAP commands. In normal protocol, the type of a client command can be written such as `Connection -> Request -> IO Response'. However, IMAP server may also return a status update data.

The server program can notify its clients with a status update untagged data. Assume that there is a client connecting to a server and the server may receive a new mail during the connection, for example. In normal, such newly received mail cannot be read from the client because the connection is established before the reception of the mail. However, the server may notify the client as that `it has a new mail'. Any commands may have such status update data.

Therefore, the type of a IMAP command should be `Connection -> Request -> IO (Response, Maybe StatusUpate)', but such typing will be annoying in many case...
Certainly, such typing can be hidden with StateT or WriterT. But is it clever approach?

BTW, I test the behavior of IMAP with Courier-IMAP, an implementation of IMAP, but it will not send any status update data with a command except NOOP. mmm....

Submitted by Derek Elkins on Thu, 06/08/2006 - 1:49pm.

What do you intend to do with the status updates typically and as an exception? If you just want to collect them, then WriterT seems appropriate. If you want to respond to them in some way that doesn't really affect the flow of the code, you could parameterized by a handler (perhaps using a ReaderT to carry around a "default" handler), i.e.

type Handler = StatusUpdate -> IO ()
type Command = Connection -> Request -> IO (Response,Maybe StatusUpdate)
type SimpleCommand = Connection -> Request -> ReaderT Handler IO Response

cmdWrapper :: Command -> SimpleCommand
cmdWrapper cmd = do
    handler <- ask
    (response,maybeUpdate) <- cmd
    case maybeUpdate of
        Just update -> handler update
        Nothing -> return ()
    return response

You can lift Commands when you need more specific, complicated handling. You can use local to locally install a new handler.

Submitted by jmuk on Fri, 06/09/2006 - 9:09am.

My intention is that the library should not hide StatusUpdate data. The library should pass such data to its user (programmer), and such data should be handled in its program.
However, proper covering of unneedful data (like your code) will make the usage of the library simple, certainly.

The best solution may be providing both approach in a library.

Submitted by Derek Elkins on Sun, 06/11/2006 - 11:24am.

The StatusUpdate was somewhat exposed in the previous but I think I have a better solution still. When I was reading about this the thought that strongly resonated in my head was "exceptions", but they (normally) are clearly not appropriate. However, resumable exceptions, or resumptions to pick a name with different connotations, do seem to fit the bill rather well. I believe there's a Resumption monad transformer in one of the monad librarys, but here's an (untested) implementation.

newtype ResumeT e r m a
   = ResumeT { runResumeT :: m (Either (e,r -> ResumeT e r m a) a) }

instance Monad m => Monad (ResumeT e r m a) where
    return = ResumeT . return . Right
    ResumeT m >>= f
        = ResumeT (liftM (either (\(e,g) -> 
              ResumeT (return (Left (e,\r -> g r >>= f))))
              (return . Right)
               m)

instance MonadTrans (ResumeT e r) where
    lift = ResumeT . liftM Right

pause :: e -> ResumeT e r m r
pause e = ResumeT (return (Left (e,return)))

You could either have your code use this monad or require the user code to. Assuming the former case, you would make a wrapper over the "primitives" that return a Maybe StatusUpdate which instead call pause with the StatusUpdate. The user will then get your IMAP code paused; they can handle the StatusUpdate however they like and then resume your code where you left off giving a value that pause will return. This leaves the user code completely free form and keeps you from having to do any bookkeeping. It is easy to provide higher level wrappers over this to make handling this even simpler when less control is needed by the user.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.