Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I do logging in Haskell?

I'm attempting to use HSlogger to get some information about my program. So I add the following line to my function

import Data.Word import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import Data.Bits import Data.Int import Data.ByteString.Parser  import System.Log.Logger import System.Log.Handler.Syslog   importFile :: FilePath -> IO (Either String (PESFile )) importFile n = do      warningM "MyApp.Component2" "Something Bad is about to happen."      ... 

And that works fine, because the function is inside IO. However when I add a similar line to the following function:

... parsePES :: Parser PESFile parsePES = do         header <- string "#PES"         warningM "parsing header"         ...         return (PESFile ...) 

I get a type error:

 Couldn't match expected type `Parser a0'                 with actual type `String -> IO ()'     In the return type of a call of `warningM'     In a stmt of a 'do' expression: warningM "parsing header"     In the expression:       do { header <- string "#PES";            warningM "parsing header";         ... 

And I totally understand why - parsePES is in the Parser monad, not the IO monad. What I don't understand is what to do about it. Do I need a monad transformer so I can stack the Parser monad and the IO monad together? How do I go about that?

like image 994
nont Avatar asked Jun 10 '11 19:06

nont


1 Answers

First, a quick disclaimer: "logging" doesn't usually make sense in general Haskell code, because it assumes some sort of sequential execution that may or may not be meaningful. Make sure you distinguish between logging how the program executes and logging what values are computed. In strict imperative languages these are mostly the same, but in Haskell they aren't.

That said, it sounds like you want to log based on values being computed, in the context of an already sequential and stateful computation, which pretty much works the same as logging in most other languages does. However, you do need the monad to support some means of doing so. It looks like the parser you're using is from the HCodecs package, which seems to be relatively limited, doesn't allow IO, and isn't defined as a monad transformer.

Honestly my advice would be to consider using a different parsing library. Parsec tends to be kind of the default choice, and I think attoparsec is popular for specific purposes (which might include what you're doing). Either would let you add logging much more easily: Parsec is a monad transformer, so you can put it on top of IO and then use liftIO as needed, whereas attoparsec is designed around incremental processing, so you can chunk your input and log aspects of the processing (though logging inside the actual parser may be more awkward). There are other choices as well but I don't know enough of the details to make a recommendation. Most parser combinator-based libraries tend to have fairly similar designs, so I'd expect porting your code would be straightforward.

A final option, if you really want to stick to what you've got, would be to look at the implementation of the parsing library you're using now and roll your own IO-oriented version of it. But that's probably not ideal.


Also, as an addendum, if you what you're really after isn't actually logging but just tracing the execution of your program as part of development, you might find the debugger built into GHCi to be more helpful, or good old-fashioned printf debugging via the Debug.Trace module.


Edit: Okay, sounds like you have plausible reasons to consider rolling your own variation. What you roughly want here is a ParserT monad transformer. Here's the current definition of Parser:

newtype Parser a = Parser { unParser :: S -> Either String (a, S) } 

The type S is the parser state. Note that this is roughly a hard-coded version of StateT S (Either String) a:

newtype StateT s m a = StateT { runStateT :: s -> m (a,s) } 

...where Either String is being treated as an error monad. The ErrorT monad transformer does the same thing:

newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) } 

So where the current type is equivalent to StateT S (ErrorT String Identity), what you want would be StateT S (ErrorT String IO).

It looks like most of the functions in the module aren't messing with the internals of the Parser monad, so you should be able to simply replace the type definitions, supply the appropriate type class instances, write your own runParser function, and be good to go.

like image 64
C. A. McCann Avatar answered Oct 05 '22 09:10

C. A. McCann