在Yesod / Haskell中,如何使用IO中的数据和变量插值功能?

时间:2015-06-04 22:08:49

标签: haskell io monads yesod

如何从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

1 个答案:

答案 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的实例不一定是变换器,IOliftIO = id的实例。这两个函数旨在让您在变换器堆栈中“拉”动作,对于堆栈中的每个级别,您可以调用liftliftIO一次。

对于您的情况,您拥有堆栈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,则可以帮助减少需要键入的字符数。