Let's have a look at Haskell's monadic solution. The idea behind logging is that our computations have an additional method that writes a message somewhere "out". There are many ways how to represent such computations, but one of the most general is to make a monad:
class (Monad m) => MonadWriter w m | m -> w where
tell :: w -> m ()
Type w
represents the messages and function tell
is what "sends" a message into a monadic (effect-full) computation.
Notes:
- Haskell's
MonadWriter
is actually richer, it contains functions that allow to examine and modifyw
, but let's keep that aside for now. - The
| m -> w
part isn't really important for the explanation, it just means thatw
is fixed for a givenm
.
The most often used implementation is Writer
, which is basically just a pair. One element of it is the result of a computation and the other element is a sequence of written messages. (Actually it's not really a sequence, it's more general - a monoid, which defines operations for combining multiple messages into one.) You can examine Haskell's solution by looking at the Writer module. However it's written more generally, using WriterT
monad transformer, so if you're not a monad fan, it can be quite hard to read. The same thing can be done in other functional languages as well, for example see this example in Scala.
But there are other possible, more side-effect oriented (still functional) implementations of the above type class. We can define tell
to emit messages to some outside sink, like to stdout, to a file, etc. For example:
{-# LANGUAGE FunctionalDependencies, TypeSynonymInstances, FlexibleInstances #-}
instance MonadWriter String IO where
tell = putStrLn
Here we say that IO
can be used as a logging facility that writes String
s into stdout. (This is just a simplified example, a full implementation would probably have a monad transformer that would add tell
functionality to any IO
-based monad.)