子站点类型之谜

Mystery of subsite types

在从身份验证插件实现类型 classes 时,我无法弄清楚我的 Foundation.hs 中应该包含哪些类型/它使用 auth 子站点:

我能感觉到我很亲近,但我缺乏理解。我只是想为 login/registration 页面使用不同的布局。

在Foundation.hs中:


instance YesodAuthSimple App where
    type AuthSimpleId App = UserId

    ...

    -- Works
    onRegisterSuccess :: YesodAuthSimple site => AuthHandler site Html
    onRegisterSuccess = authLayout $ [whamlet|
      $newline never
      <div>
        <h1>You Registered successfully.
        <p>
          Some text here.
    |]  

    -- Works when I do not write a type signature
    loginTemplate toParent mErr = $(widgetFile "authpartials/login")


    -- Does not work with or without type signatures
    customAuthLayout widget = do 
        master <- getYesod
        mmsg <- getMessage
        muser <- maybeAuthPair
        mcurrentRoute <- getCurrentRoute
        pc <- widgetToPageContent $ do
            $(widgetFile "custom-auth-layout")
        withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")

432:15 指的是 widgetToPageContent 调用。

在类型class定义中Simple.hs:

class (YesodAuth site, PathPiece (AuthSimpleId site)) => YesodAuthSimple site where
  type AuthSimpleId site

  ...

  customAuthLayout :: WidgetFor site () -> AuthHandler site Html

  ...

我粘贴了 customAuthLayout 来自 defaultLayout 来自 Foundation.hs

的定义

这是我从 GHC 得到的错误:

Foundation.hs:432:15: error:
    • Could not deduce: m ~ HandlerFor App
      from the context: MonadAuthHandler App m
        bound by the type signature for:
                   customAuthLayout :: WidgetFor App () -> AuthHandler App Html
        at src/Foundation.hs:(427,5)-(434,79)
      ‘m’ is a rigid type variable bound by
        the type signature for:
          customAuthLayout :: WidgetFor App () -> AuthHandler App Html
        at src/Foundation.hs:(427,5)-(434,79)
      Expected type: m (PageContent (Route App))
        Actual type: HandlerFor App (PageContent (Route App))
    • In a stmt of a 'do' block:
        pc <- widgetToPageContent
                $ do (do do (asWidgetT GHC.Base.. toWidget)
                              ((blaze-markup-0.8.2.2:Text.Blaze.Internal.preEscapedText
                                  GHC.Base.. Data.Text.pack)
                                 "<!--  custom-auth-layout -->
<body class="d-flex align-items-center bg-auth border-top border-top-2 border-primary">")
                            ....)
      In the expression:
        do master <- getYesod
           mmsg <- getMessage
           muser <- maybeAuthPair
           mcurrentRoute <- getCurrentRoute
           ....
      In an equation for ‘customAuthLayout’:
          customAuthLayout widget
            = do master <- getYesod
                 mmsg <- getMessage
                 muser <- maybeAuthPair
                 ....
    |
432 |         pc <- widgetToPageContent $ do
    |               ^^^^^^^^^^^^^^^^^^^^^^^^...


我已经成功地将本教程用于正常(非子站点页面)https://ersocon.net/cookbooks/yesod/html-and-seo/custom-layouts

但是我被子网站类型搞糊涂了。我已经阅读了 Michael Snoyman 关于子站点类型的非常好的旧博客 post,但我无法理解 GHC 的错误消息。

我怀疑 Simple.hs 中的类型签名有误,或者我在函数定义中遗漏了某些内容。

尝试在widgetToPageContent前加上liftHandler:

...
pc <- liftHandler $ widgetToPageContent $ do
    $(widgetFile "custom-auth-layout")
...

错误消息中的关键行是:

  Could not deduce: m ~ HandlerFor App
  ...
  Expected type: m (PageContent (Route App))
    Actual type: HandlerFor App (PageContent (Route App))

它基本上是在告诉我们,它需要一个更通用的类型 m,但它却得到了 HandlerFor App。所以解决方案就是使用 liftHandler 函数 提升 widgetToPageContent 的调用。

进一步详细说明,如果我们查看函数 widgetToPageContent 的类型签名,我们会看到它 returns HandlerFor site (PageContent (Route site))。在这种情况下,site 实例化为 App,也就是您在错误消息中看到的 HandlerFor App (PageContent (Route App))

同样,您的 customLayout 函数 returns AuthHandler site HtmlAuthHandler 只是一个类型同义词,它将 site 限制为等同于 HandlerSite m 的类型,这也是 YesodAuth 的一个实例。这也解析为 App,这就是我们在错误消息中得到 MonadAuthHandler App mm (PageContent (Route App)) 的原因。