Search code examples
haskellyesodtemplate-haskell

Mystery of subsite types


I can not figure out what types should go in my Foundation.hs when implementing type classes from the authentication plugin / it's use of the auth subsite:

I can feel that I am very close, but I lack understanding. I am simply trying to use a different layout for the login/registration pages.

In 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")

The 432:15 is referring to the widgetToPageContent call.

In the type class definition Simple.hs:

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

  ...

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

  ...

I pasted in the definition of customAuthLayout from defaultLayout from Foundation.hs

Here is the error I get from 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
    |               ^^^^^^^^^^^^^^^^^^^^^^^^...


I have used this tutorial successfully for normal (non-subsite pages) https://ersocon.net/cookbooks/yesod/html-and-seo/custom-layouts

But I am getting tripped up by the subsite types. I have read Michael Snoyman's very good old blog post on subsite types but I cannot understand GHC's error message.

I suspect either the type signature in Simple.hs is wrong, or I am missing something from the function definition.


Solution

  • Try to add liftHandler before widgetToPageContent:

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

    Key lines in the error message are:

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

    It is basically telling us that it expected a more generic type m, but instead it got a HandlerFor App. So the solution is simply to lift the call to widgetToPageContent using the liftHandler function.

    To elaborate further, if we look at the type signature of the function widgetToPageContent, we see that it returns HandlerFor site (PageContent (Route site)). In this case, site instantiates to App, and that is the HandlerFor App (PageContent (Route App)) you see in the error message.

    Similarly, your customLayout function returns AuthHandler site Html. AuthHandler is just a type synonym that constrains site to a type equivalent to HandlerSite m which is also an instance of YesodAuth. This also resolves to App, and that is why we get MonadAuthHandler App m and m (PageContent (Route App)) in the error message.