【问题标题】:How to do IO in a Widget/Hamlet referenced by defaultLayout?如何在 defaultLayout 引用的 Widget/Hamlet 中进行 IO?
【发布时间】:2016-07-11 20:27:11
【问题描述】:

我是 Yesod 的新手,似乎对小部件、处理程序、Hamlets、WHamlets 以及你有什么完全迷失了!这是我正在尝试做的事情:

  • 我网站上的每个页面都需要有一个导航栏,这让我相信实现它的正确位置应该是defaultLayout
  • 现在,此导航栏需要显示一些从 IO 操作中获得的信息(更具体地说,它是一个提供此数据的 RPC 调用)。

因此,我尝试在Foundation.hs 中编写以下函数(代码布局是基本的yesod-sqlite 脚手架模板):

nav = do
  globalStat <- handlerToWidget $ A2.getGlobalStat NWT.ariaRPCUrl
  $(whamletFile "templates/navbar.hamlet)

A2.getGlobalStat :: IO GlobalStatResponse

template/navbar.hamlet 是这样的:

<nav .navbar .navbar-default>
  <div .container-fluid>
    <p .navbar-right .navbar-text>
      <span>
        #{A2.glDownloadSpeed globalStat}
        <i .glyphicon .glyphicon-arrow-down>
      <span>
        #{A2.glUploadSpeed globalStat}
        <i .glyphicon .glyphicon-arrow-up>
      <span .label .label-success>
        On-the-watch

default-layout-wrapper.hamlet 是这样的:

<!-- SNIP -->
  <body>
    <div class="container">
      <header>
        ^{nav}
      <div id="main" role="main">
        ^{pageBody pc}
<!-- SNIP -->

defaultLayout 是这样的:

defaultLayout widget = do
    master <- getYesod
    mmsg <- getMessage
    pc <- widgetToPageContent $ do
        addStylesheet $ StaticR css_bootstrap_css
        $(widgetFile "default-layout")
    withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")

但是,代码拒绝编译一个又一个类型错误。我尝试了很多hametFilewhamletFilehanderToWidgetliftIO 的组合,甚至将导航功能 inside defaultLayout,但似乎没有任何效果。据我说,我当前的代码应该编译,但我显然不明白 Yesod-Core 类型是如何工作的。

如何让它工作?更重要的是,我误解了什么概念?

编辑 1:

已尝试将nav 函数修改为以下内容:

nav :: Handler Html
nav = do
  globalStat  <- liftIO $ A2.getGlobalStat NWT.ariaRPCUrl
  $(hamletFile "templates/navbar.hamlet")

但是,它会导致defaultLayoutwithUrlRenderer 的行出现以下类型不匹配:

 Couldn't match type ‘HandlerT App IO Html’
                with ‘Text.Hamlet.Render (Route App) -> Html’
 Expected type: HtmlUrl (Route App)
   Actual type: Handler Html
 In the first argument of ‘Text.Hamlet.asHtmlUrl’, namely ‘nav’
 In a stmt of a 'do' block: Text.Hamlet.asHtmlUrl nav _render_a2ZY0 (intero)

编辑 2:

尝试将nav 的类型签名更改为:

nav :: Widget
nav = do
  globalStat  <- liftIO $ A2.getGlobalStat NWT.ariaRPCUrl
  $(hamletFile "templates/navbar.hamlet") 

但它会在同一行中导致新的类型不匹配:

 Couldn't match type ‘WidgetT App IO ()’
                with ‘Text.Hamlet.Render (Route App) -> Html’
 Expected type: HtmlUrl (Route App)
   Actual type: Widget
 In the first argument of ‘Text.Hamlet.asHtmlUrl’, namely ‘nav’
 In a stmt of a 'do' block: Text.Hamlet.asHtmlUrl nav _render_a350l (intero)

编辑 3:

这是来自-ddump-splices的相关sn-p:

\ _render_a28TE
  -> do { asHtmlUrl (pageHead pc) _render_a28TE;
          id ((Text.Blaze.Internal.preEscapedText . Data.Text.pack) "\n");
          asHtmlUrl (pageBody pc) _render_a28TE;
          id ((Text.Blaze.Internal.preEscapedText . Data.Text.pack) "\n");
          asHtmlUrl testWidget2 _render_a28TE }

(pageHead pc)(pageBody pc)的类型是HtmlUrl (Route App)

【问题讨论】:

  • 编辑了我的问题以发布基于liftIO 方法的新代码 sn-ps。现在,问题似乎是 default-layout-wrapper 模板中的 ^{nav} 用法似乎没有很好地组合。

标签: haskell yesod shakespeare-text


【解决方案1】:

查看this SO question 的答案。基本上你不能在模板中执行 IO。

还要注意defaultLayout的类型是GHandler ...GHandlerinstance of MonadIO,所以你可以在defaultLayout中使用liftIO来执行IO。

我会尝试:

defaultLayout = do
  ...
  globalStat <- liftIO $ handlerToWidget $ A2.getGlobalStat NWT.ariaRPCUrl
  uploadSpeed <- liftIO $ A2.glUploadSpeed globalStat
  downloadSpeed <- liftIO $ A2.glDownloadSpeed globalStat
  ...
  withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")

templates/default-layout-wrapper.hamlet:

...
^{nav uploadSpeed downloadSpeed}
...

nav 变成这样:

nav uploadSpeed downloadSpeed =   $(whamletFile "templates/navbar.hamlet)

所以基本思路是:

  • 使用liftIOdefaultLayout 中执行所有IO
  • 将子模板所需的数据作为函数参数传递

更新

要模拟this example in the Yesod book,您需要像这样写navbar

navbar :: Widget
navbar = do
    globalStat <- liftIO A2.getGlobalStat NWT.ariaRPCUrl
    downloadSpeed <- liftIO A2.glDownloadSpeed globalStat
    uploadSpeed <- liftIO A.glUploadSpeed
    $(whamletFile "templates/navbar.hamlet)

navbar.whamlet 中,请参考#{uploadSpeed}#{downloadSpeed}

您不能在 whamlet 文件中执行 IO。此外,您的 A2 函数是 IO 操作,但 handlerToWidget 需要 HandlerT 操作,因此您需要使用 liftIO 来转换这些调用。

更新 2

请参阅http://lpaste.net/169497 了解在 Widget 中执行 IO 的工作示例。

【讨论】:

  • 感谢@ErikR 的回答。你对yesodweb.com/book/… 有什么看法?我想做的不就是类似吗?
  • 谢谢,ErikR。它仍然不适合我,但至少我已经取得了一些进展。您对使用handlerToWidget $ liftIO 的建议导致nav 函数的类型更简单,即nav :: (MonadIO m) =&gt; WidgetT site m ()。然而,这让我想知道为什么liftIO 首先需要handlerToWidget。为什么liftIO 不能将 IO 操作直接提升到 WidgetT site m () 变压器堆栈?
  • 我的预感是正确的。 handlerToWidget 实际上不是必需的。因为我没有给出顶级类型声明,liftIO 不知道将 IO 操作提升到哪个 monad。只需将 nav :: Widget 添加为顶级类型声明,即可在没有 handlerToWidget 的情况下编译 nav 函数。
  • 感谢 ErikR。我已经设法在 Handler 操作中执行 IO,但不是在我所针对的原始代码中。我在lpaste.net/169497#a169504 的工作示例中重现了相同的错误,请注意defaultLayout 实现(这是yesod-sqlite 脚手架提供的)。只要将defaultLayout 更改为此定义,lpaste-wrapper.hamlet 就无法调用^{testWidget2}^{testWidget}
  • -ddump-splices 的输出可能有用吗?请看一下我的最新编辑。
【解决方案2】:

这是我如何让它工作的。实际上我面临两个不同的问题:

  • 在 Widget 中执行 IO
  • default-layout-wrapper hamletFile 中引用一个小部件。

这是在小部件内进行 IO 的解决方案:

nav :: Widget
nav = do
  globalStat <- liftIO $ A2.getGlobalStat NWT.ariaRPCUrl
  $(whamletFile "templates/navbar.hamlet")

注意: 类型签名nav :: Widget 似乎是必要的,否则类型推断引擎可能会混淆,并为liftIO 操作推断出一个非常不同的类型(最初发生在我)。

对于第二个问题,我真的找不到在default-layout-wrapper hamletFile 中引用小部件的解决方案。在渲染这个特定的 hamletFile 时,Widget monad 已转换为 PageContent 类型,现在它需要 Html url 类型才能与 withUrlRenderer 函数一起渲染它。基本上,我无法让WidgetPageContent 撰写。但是,以下方法以不同的方式给了我想要的结果:

default-layout.hamlet:在此文件中添加了对 nav 小部件的调用。将 default-layout-wrapper 中的一些元素移至此文件:

<div .container>
  <header>
    ^{nav}
  <div #main role="main">
    $maybe msg <- mmsg
      <div #message>#{msg}
    ^{widget}

default-layout-wrapper.hamlet: 将此文件中的一些 HTML 元素移动到 default-layout

<!-- SNIP -->
  <body>
    <div class="container">
      ^{pageBody pc}
<!-- SNIP -->

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-12
    • 1970-01-01
    • 1970-01-01
    • 2013-01-18
    • 2013-04-16
    • 2021-08-18
    • 1970-01-01
    相关资源
    最近更新 更多