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
source to share
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.
source to share