在 Yesod/Haskell 中,如何使用具有变量插值功能的 IO 数据?
In Yesod/Haskell, how do I use data from IO with the variable interpolation functionality?
如何从 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
使用 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
的实例。这两个函数旨在让您 "pull" 对转换器堆栈进行操作,对于堆栈中的每个级别,您将调用 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
,这可以帮助 trim 减少您需要键入的字符数。
如何从 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
使用 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
的实例。这两个函数旨在让您 "pull" 对转换器堆栈进行操作,对于堆栈中的每个级别,您将调用 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
,这可以帮助 trim 减少您需要键入的字符数。