Logging from within a Functional Programming Paradigm
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.)
I'm new to functional programming, but here's an attempt in Scala:
object FunctionalLogging {
type Result = Int
class ResultWithLogging(val log: List[String], val result: Result) {}
def functionWithLogging(log: List[String], arg: String): ResultWithLogging = {
def function(arg: String): Result = arg.length
new ResultWithLogging(log :+ ("Calling function(" + arg +")"), function(arg))
}
val result = functionWithLogging(List(), "Hello world!")
// -- Pure functional code ends here --
println("Result = " + result.result)
println("Log = " + result.log)
}
It's functional in that there are no side effects, but obviously the log is part of the function arguments and return, so it's not very elegant or practical.
It seems to me that logging is a desirable side-effect by definition, so if you go along with my definition the question is how to isolate the non-functional from the functional code. In practice I would probably start with a Scala object (possibly too much like a singleton - a trait is probably better Scala), or an actor to accumulate the logging messages and do whatever needs to be done with them.
This is a more pragmatic view: Logging in Scala
Edit
This question talks about Haskell Monads and IO: What other ways can state be handled in a pure functional language besides with Monads?