Monad transformer stack confusion - Haskell

Welcome to the Functional Programming Zulip Chat Archive. You can join the chat here.

Sridhar Ratnakumar

Newbie question. I can't believe I'm still struggling to grapple MTL stuff. The lucid library has HtmlT m a which has a MonadState instance. But how exactly do you use it? I tried flip runState mempty someFunc where someFunc is MonadState String m => HtmlT m () but that doesn't compile.

Sridhar Ratnakumar

StateT String (HtmlT Identity) () seems one step forward, but then you have to lift all UI elements, thus losing access to state.

Sridhar Ratnakumar

Aha, flip runStateT mempty $ evalHtmlT someFunc works.

Sridhar Ratnakumar

Grr, but that doesn't render the Html at runtime

Sridhar Ratnakumar

Okay, after reading the source ... unpack and pack it back: HtmlT $ fmap fst $ flip runStateT mempty $ runHtmlT someFunc

Sandy Maguire

you're doing something cray here!

Sandy Maguire

monad transformers should only need to be unpeeled in main

Sandy Maguire

flip runState myState $ runHtmlT myHTMLFUNCTION

Sandy Maguire

the State here is global

Sandy Maguire

if you want local state semantic, you want to build a StateT s Html instead

Sridhar Ratnakumar

With StateT s Html you would have lift when rendering UI, but at that point - access to state is lost.

Sridhar Ratnakumar

And it seems that the lucid library recommends putting State inside the HtmlT transformer: https://hackage.haskell.org/package/lucid-2.9.12/docs/Lucid.html#t:HtmlT

Sridhar Ratnakumar

So to "inject" an inner monad, I had to peel the skin, apply the topical cream (runState), then put it back.

Sridhar Ratnakumar

(well, that reads pretty gory)

Vincent L

Hi, I have an issue with monad transformer.
Here is the code that I wrote

collectRendu :: [String] -> IO [Rendu]
collectRendu candidate_subdir_names =
    do
      dirs <- listDirectory rootDirectory
      catMaybes <$> mapM aux dirs
    where
        aux:: FilePath -> IO (Maybe Rendu)
        aux dir = do
          candidate <- headMaybe <$> filterM (doesRenduExists dir) candidate_subdir_names
          case candidate of Nothing -> return Nothing
                            Just x -> Just <$> appendNewRendu dir x
        doesRenduExists studentNamedDir = doesDirectoryExist . ((rootDirectory </> studentNamedDir) </>)

the part that doesn't please me is the "case candidate of...".
Candidate type is "Maybe Filepath", I want to return a "IO (Maybe Rendu)", and type of appendNewRendu is "FilePath -> FilePath -> IO Rendu".
I first used fmap until I realised that "(appendNewRendu dir) <$> candidate" will yield me a "Maybe (IO Rendu)".

I suspect there is a better way, but I'm a bit stuck. Since IO and Maybe are stacked monad I suspect there is something to do with monad transformers, but I didn't used them that much.

Vincent L

Asking for "(a -> m b) -> Maybe a -> m Maybe b" on Hoogle didn't yield any result

Vincent L

ho nvm if I correctly put parenthesis it works

TheMatten

Don't bother with transformers just to remove a little bit of boilerplate in single function, I don't think it's worth it from readability perspective
In this case, you can simply use traverse:

traverse (appendNewRendu dir) candidate
Vincent L

it also looks like mapM works here

Vincent L

traverse and mapM are the same ?

TheMatten

Yeah, because of laws
mapM is more of a legacy thing

Vincent L

out of curiosity there is a way to use lift that would work here ? I very rarely use monad transformers and still lack the intuition

TheMatten

There's MaybeT:

newtype MaybeT m a = MaybeT (m (Maybe a))

which would probably work something like this:

aux dir = runMaybeT do
  candidate <- MaybeT $ headMaybe <$> filterM (doesRenduExists dir) candidate_subdir_names
  lift $ appendNewRendu dir candidate
Vincent L

MaybeT is not "automatic" by default ?

Vincent L

I mean I never need to add ReaderT or WriterT or StateT

TheMatten

You mean MaybeT constructor in example above?

TheMatten

well,

headMaybe <$> filterM (doesRenduExists dir) candidate_subdir_names
  :: IO (Maybe String)

which means that it doesn't fit m String for some m containing MaybeT
You're basically making use of fact that internal implementation of MaybeT uses Maybe - it could just as well use something different, and so compiler has no reason to coerce it into MaybeT by itself

Torsten Schmits

it just seems automatic because you're usually using more complex combinators for Reader etc