如何从IO monad中获取值并插入到yesod小部件中?
例如,我想将文件的内容插入到hamlet中:
(readFile "test.txt") >>= \x -> toWidget $ [hamlet| <p> text: #{x} |]
或等效地:
contents <- (readFile "test.txt")
toWidget $ [hamlet| <h2> foo #{contents} |]
有一些基本的东西我没有理解插值如何与IO交互,因为这些类型检查都没有:
Couldn't match type ‘IO’ with ‘WidgetT App IO’ …
Expected type: WidgetT App IO String
Actual type: IO String
这些错误发生在getHomeR路由函数中。
如果我尝试使用预定义函数在GHCi中执行类似的操作,我会收到不同的错误。在源文件中,我有一个函数:
makeContent body =
[hamlet|
<h2> foo
<div> #{body}
|]
在GHCi中:
(readFile "test.txt") >>= \x -> makeContent x
由于论证不充分而导致错误(我认为这是因为我还不了解一些模板魔法):
<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
答案 0 :(得分:1)
使用monad变换器时,要从一些monad m
转换为变换器t m
,您必须使用lift
函数:
lift :: (MonadTrans t, Monad m) => m a -> t m a
这实际上是MonadTrans
类型类的定义方法,因此实现特定于变换器t
。
如果要在变换器中执行IO
操作,则必须为MonadIO
定义一个具有liftIO
方法的实例:
liftIO :: (MonadIO m) => IO a -> m a
MonadIO
的实例不一定是变换器,IO
是liftIO = id
的实例。这两个函数旨在让您在变换器堆栈中“拉”动作,对于堆栈中的每个级别,您可以调用lift
或liftIO
一次。
对于您的情况,您拥有堆栈WidgetT App IO
,包含转化器WidgetT App
和基本monad IO
,因此您只需拨打一次liftIO
即可IO
行动最后成为WidgetT App IO
monad中的行动。所以你要做的就是
liftIO (readFile "test.txt") >>= \x -> makeContent x
许多开发人员(包括我自己)在你有很多liftIO
行动时会发现IO
有点负担,所以看到像
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
等等。如果您发现自己在代码中使用了大量liftIO
s,则可以帮助减少需要键入的字符数。