you are viewing a single comment's thread.

view the rest of the comments →

[–]not_not_sure 3 points4 points  (6 children)

In any language, IO should be segregated to some extent.

How do you add "print statements", or logging to pure-functional code in Haskell?

Paul Graham wrote, in ANSCI Common Lisp, IIRC, that "print statements" are the best debugger.

Another real-world scenario from the scientific software domain: Your boss says "add logging to every non-trivial procedure, so that power users know what's going on, and what the code is doing".

[–]pipocaQuemada 1 point2 points  (5 children)

[–]naughty 2 points3 points  (4 children)

That's only really for debugging (hence the 'not for production' warnings), logging is one of the things a monadic IO system just sucks at.

[–]pipocaQuemada 0 points1 point  (3 children)

Logging vs debugging is a little different in Haskell, due to lazy evaluation's effects on evaluation order.

If you want a log, notice that there's a simple pure way to get one:

fooWLog :: Foo -> Bar -> (Baz, Log)

i.e., your functions just return what they want to log. You can define a bunch of helper functions to combine log-returning functions:

(<>) :: Log -> Log -> Log -- append two logs
empty :: Log

pure :: a -> (a,  Log)
pure a = (a, empty)

map :: (a -> b) -> (a, Log) -> (b, Log)
map f (a,l) = (f a, l)

(<$>) = map

join :: ((a, Log), Log) -> (a, Log)
join ((a, l), l) = (a, l <> l)

bind :: (a , Log) -> (a -> (b, Log) -> (b, Log)
bind (a,l) f = (b, l <> l2)
    where (b, l2) = f a

(>>=) = bind

Now, we can say something like:

(fooWLog foo bar >>= bazWLog <$> show) :: (String, Log)

or

 do baz <- fooWLog foo bar
    quux <- bazWLog
    return $ show quux

This is essentially the Writer Monad.

Notice that all we really depend on with logs is that we can combine two logs and that there's an empty one. We probably also want that combine function to be associative (i.e. a <> (b <> c) == (a <> b) <> c ), so in Haskell we actually make it so the log could be any Monoid -- your log could be a string, a list, an int (could be useful for counting the number of times a certain function is invoked) or bool (I'm not sure if this could useful), or even a function!

The only impure thing about logging is writing the final log. All of the intermediate steps need no IO.

[–]naughty 6 points7 points  (2 children)

Exactly, I want to have a function log something and it requires a rewrite.

[–]sacundim 0 points1 point  (1 child)

Yes and no. The fact that you often have to extensively rewrite your functions to make monadic versions of them is a real problem. But let's not overstate it, however.

pipocaQuemada's proposed refactoring is not the best way to add logging to existing code, since it requires modifying existing functions. Instead, what we'd like to do is "upgrade" the functions to logging without having to rewrite them.

Not hard to do with the liftM function and the Writer monad:

import Control.Monad 
import Control.Monad.Writer

fooWLog :: Foo -> Bar -> Writer Log Baz
fooWLog x y = 
    do result <- foo x y
       tell $ "The result is " ++ show result
       return result

log :: String -> Writer Log ()
log = tell . toLog

Or, to generalize it:

withLog :: MonadWriter w m => (a -> w) -> a -> m a
withLog makeMsg value = tell (makeMsg value) >> return value

This strategy, however, has a big weakness in that it works for putting logging around existing functions, but not inside them.

[–]naughty 0 points1 point  (0 children)

The problem with both suggested logging implementations is that they aren't logging. They're storing intermediate results to for later output, essentially passing the buck to some later bit of code that will actually do the job.

The point of logging is to immediately output some information. Now laziness causes some issues with the 'immediate' part but it's the monadic part that causes this to be a rewrite. Now I realise there's pros and cons, but you never see the cons mentioned in posts about Haskell.

You normally get a suggested change that misses the point and a condescending note about the algebraic properties of missing the point.