Search code examples
haskellyesodtemplate-haskell

Evaluation of template haskell in Yesod


While going through the examples of the Yesod Book, I'm running into an issue with the following snippet:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}
{-# LANGUAGE ViewPatterns      #-}
import           Data.Text (Text)
import qualified Data.Text as T
import           Yesod

data App = App
instance Yesod App

mkYesod "App" [parseRoutes|
/person/#Text PersonR GET
/year/#Integer/month/#Text/day/#Int DateR
/wiki/*Texts WikiR GET
|]

getPersonR :: Text -> Handler Html
getPersonR name = defaultLayout [whamlet|<h1>Hello #{name}!|]

handleDateR :: Integer -> Text -> Int -> Handler Text -- text/plain
handleDateR year month day =
    return $
        T.concat [month, " ", T.pack $ show day, ", ", T.pack $ show year]

getWikiR :: [Text] -> Handler Text
getWikiR = return . T.unwords

main :: IO ()
main = warp 3000 App

(It's on page 124 of 598; route arguments)

The instance declaration on line 11 raises the following error:

YesodRouteParams.hs:11:10: error:
    • No instance for (RenderRoute App)
        arising from the superclasses of an instance declaration
    • In the instance declaration for ‘Yesod App’
   |
11 | instance Yesod App
   |

It can be fixed by moving that line below the mkYesod block, where routes are defined.

I'm trying to understand why that is. Does it mean that Template Haskell evaluation at compile time happens simultaneously with the written code evaluation?

I ask because in Crystal, for example, macros are expanded before anything else. So the order of things doesn't really matter in a file (or app). But by the looks of it, they do in Haskell. Or is there another explanation?


Solution

  • This was because of a change made in GHC 9.0.1. From the release notes:

    Breaking change: Template Haskell splices now act as separation points between constraint solving passes. It is no longer possible to use an instance of a class before a splice and define that instance after a splice. For example, this code now reports a missing instance for C Bool:

    class C a where foo :: a
    bar :: Bool
    bar = foo
    $(return [])
    instance C Bool where foo = True
    

    If you were to downgrade to GHC 8.10.7, you'd see that your code would then work as you wrote it.

    I opened https://github.com/yesodweb/yesodweb.com-content/pull/269 to fix the examples in the book.