MonadTransformer exercise question

97 views
Skip to first unread message

David Fries

unread,
Jun 17, 2009, 1:10:28 PM6/17/09
to Real World Haskell Book Club
Hi guys

I have problems wrapping my head around the 3rd exercise (on page 436)
in chapter 18. The task is to "rewrite the constrainedCount function
to record results using the WriterT transformer in your new App
stack." I do have a working solution, as well as an alternative
solution which doesn't work. But I don't understand why. Perhaps
someone could explain it to me.

I defined a new type synonym AppWrite in terms of App:

type App = StateT AppState (ReaderT AppConfig IO)

type AppWrite = WriterT [(FilePath, Int)] App

I also updated the MyApp newtype and the runner function like this:

newtype MyApp a = MyA {
runA :: WriterT [(FilePath, Int)] (StateT AppState (ReaderT
AppConfig IO)) a
} deriving (Monad, MonadIO, MonadReader AppConfig,
MonadState AppState, MonadWriter [(FilePath, Int)])

runMyApp :: MyApp a -> Int -> IO ((a, [(FilePath, Int)]), AppState)
runMyApp k maxDepth =
let config = AppConfig maxDepth
state = AppState 0
in runReaderT (runStateT (runWriterT (runA k)) state) config

The new (working) contrainedCount function looks like this:

constrainedCount :: Int -> FilePath -> AppWrite ()
constrainedCount curDepth path = do
contents <- liftIO . listDirectory $ path
tell [ (path, length contents) ]
cfg <- ask
rest <- forM contents $ \name -> do
let newPath = path </> name
isDir <- liftIO $ doesDirectoryExist newPath
if isDir && curDepth < cfgMaxDepth cfg
then do
let newDepth = curDepth + 1
st <- get
when (stDeepestReached st < newDepth) $
put st { stDeepestReached = newDepth }
constrainedCount newDepth newPath
else return ()
return ()

This solution works - as you would expect. Initially I tried another
solution which is identical except that I put the tell function in
another place. Namely just before the recurive call to
brokenConstrainedCount:

brokenConstrainedCount :: Int -> FilePath -> AppWrite ()
brokenConstrainedCount curDepth path = do
contents <- liftIO . listDirectory $ path
cfg <- ask
rest <- forM contents $ \name -> do
let newPath = path </> name
isDir <- liftIO $ doesDirectoryExist newPath
if isDir && curDepth < cfgMaxDepth cfg
then do
let newDepth = curDepth + 1
st <- get
when (stDeepestReached st < newDepth) $
put st { stDeepestReached = newDepth }
tell [ (path, length contents) ] -- Alternative
placement of tell
brokenConstrainedCount newDepth newPath
else return ()
return ()

However this won't compile. The compiler says: Couldn't match expected
type '[(FilePath, Int)] -> m ()' against inferred type '()' in the
second argument of '($)', namely 'put st { stDeepestReached =
newDepth } tell [ (path, length contents) ] '

I didn't know 'when', so I looked it up:
http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Monad.html#v:when
Which seemed pretty straight forward - conditional execution of
monadic expression.

Clearly, calling 'tell' doesn't type check, hence the compiler error.
What I don't understand is why the call to 'tell' got dragged into
the 'when'-condition together with 'put'.




Reply all
Reply to author
Forward
0 new messages