In Yesod / Haskell, how to use data from IO with variable interpolation functionality?

How can I take values ​​from the I / O monad and interpolate them into a Yesod widget?

For example, I want to interpolate the contents of a file in a tree:

(readFile "test.txt") >>= \x -> toWidget $ [hamlet| <p> text: #{x} |]

      

or equivalently:

contents <- (readFile "test.txt")
toWidget $ [hamlet| <h2> foo #{contents} |]

      

There is something basic, I don't understand how interpolation interacts with IO since none of these type checks are:

Couldn't match type ‘IO’ with ‘WidgetT App IO’ …
    Expected type: WidgetT App IO String
      Actual type: IO String

      

These errors occur in the getHomeR route function.

If I try to do something like this in GHCi with a predefined function, I get another error. In the source file, I have a function:

makeContent body =
    [hamlet|
        <h2> foo
        <div> #{body}
    |]

      

in GHCi:

(readFile "test.txt") >>= \x -> makeContent x

      

And I get an error because of insufficient arguments (I guess it is because of some kind of templating magic that I don't understand yet):

<interactive>:139:33:
    Couldn't match expected type ‘IO b’
                with actual type ‘t0 -> Text.Blaze.Internal.MarkupM ()’
    Relevant bindings include it :: IO b (bound at <interactive>:139:1)
    Probable cause: ‘makeContent’ is applied to too few arguments

      

+3


source to share


1 answer


When working with monad transformers, to convert from some monad m

to a transformer t m

, you must use the function lift

:

lift :: (MonadTrans t, Monad m) => m a -> t m a

      

This is actually a method for defining a class MonadTrans

, so the implementation is transformer specific t

.

If you want to do things IO

inside a transformer, you will need to define an instance for MonadIO

which has a method liftIO

:

liftIO :: (MonadIO m) => IO a -> m a

      

the instance MonadIO

does not have to be a transformer, although it IO

is an instance where liftIO = id

. These two functions are for you to "pull" actions on the transformer stack, for each level in the stack you would call once lift

or liftIO

.



In your case, you have a stack WidgetT App IO

, with a transformer WidgetT App

and a base monad IO

, so you only need one call liftIO

to pull an action IO

to be an action in a monad WidgetT App IO

. So you would just do

liftIO (readFile "test.txt") >>= \x -> makeContent x

      

Many developers (myself included) find a liftIO

bit of a burden to type when you have a lot of actions IO

, so it's not uncommon to see something like

io :: MonadIO io => IO a -> io a
io = liftIO

putStrLnIO :: MonadIO io => String -> io ()
putStrLnIO = io . putStrLn

printIO :: (MonadIO io, Show a) => a -> io ()
printIO = io . print

readFileIO :: MonadIO io => FilePath -> io String
readFileIO = io . readFile

      

Etc. If you find yourself using a lot in your code liftIO

, it can help reduce the number of characters you have to enter.

+1


source







All Articles