Reputation: 6517
This is really a beginner question, but I cannot find anything about it on the web or on stackoverflow. Maybe I just searched wrong..
I have a yesod application where everything is in one file, and I cannot figure out how to move the functions into separate files. Here is a minimal example:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Yesod
data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]
instance Yesod App
getHomeR = defaultLayout $ toWidget [hamlet|Hello Stackoverflow|]
main = warp 8080 App
How do I move the getHomeR
function into a separate file? In getHomeR
, I need access to App
, but mkYesod "App"
needs access to getHomeR
. This looks like a cyclic dependency. But somehow it must be possible to create yesod applications consisting of more than one source file.
What I can do is move functionality that is independent of App
into separate files. But when App
grows and contains more and more functionality, this becomes inconvenient, because all top level handler functions still need to be in the same file. And I don't want to use the yesod template, because I don't understand what it is doing.
to address a proposed solution from the comments:
You can define the functions in your separate files, and import these in the Main. This is more or less how it is done in the yesod-mysql stack template.
error message: "No instance for (Yesod site0) arising from a use of `defaultLayout'".
And when I import Main
in GetHomeR.hs, the error message becomes "Ambiguous type variable site0' arising from a use of
defaultLayout'".
And when I add a getHomeR :: Handler Html
, it compiles for a moment, but: now I have to import GetHomeR
from Main.hs . And GHC complains with a warning about a cyclic dependency:
Module imports form a cycle:
module `Main' (app\Main.hs)
imports `GetHomeR' (app\GetHomeR.hs)
which imports `Main' (app\Main.hs)
this doesn't feel right. Is this the right way to do it?
Upvotes: 1
Views: 165
Reputation: 6517
I finally figured it out on my own.
The mkYesodData documentation says:
Sometimes, you will want to declare your routes in one file and define your handlers elsewhere. For example, this is the only way to break up a monolithic file into smaller parts. Use this function, paired with mkYesodDispatch, to do just that.
You have to create a Foundation module where the App
and the Yesod instance lives:
Foundation.hs:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Foundation where
import Yesod
data App = App
mkYesodData "App" [parseRoutes|
/ HomeR GET
|]
instance Yesod App
In your Main.hs, you call the routes:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Yesod
import Foundation
import GetHomeR (getHomeR)
mkYesodDispatch "App" resourcesApp
main = warp 8080 App
which in turn calls getHomeR from the GetHomeR module. GetHomeR.hs:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module GetHomeR (getHomeR) where
import Yesod
import Foundation
getHomeR :: Handler Html
getHomeR = defaultLayout $ toWidget [hamlet|Hello Stackoverflow|]
mkYesod is basically like mkYesodData + mkYesodDispatch, with the difference that mkYesodData + mkYesodDispatch cannot be in the same file because of the "stage restriction" (error message: "GHC stage restriction: `resourcesApp' is used in a top-level splice, quasi-quote, or annotation, and must be imported, not defined locally")
All the route handlers (getHomeR, ...) must be visible at the point where mkYesodDispatch
(or mkYesod
) is called.
Upvotes: 2