Yesod and subsites: a no-brainer

Update 20130316: As of Yesod 1.2, this post will be obsolete since the way Yesod is treating subsites is changed. See Michael Snoyman's blog post on the matter.

I am following two main threads in my Haskelling these days:

  • immersing myself in the Yesod web framework, and
  • immersing myself in Concurrent/Parallel Haskell.

The reason for these parallel (pardon the pun) threads (ouch, again!) being I am writing a HTML5 web app where I have to use a bit of Concurrent Haskell on the server side.

For the Yesod part of things, I have invested in a dead tree copy of the Yesod Book and I am following it quite diligently. Now, for this particular day I needed to create two subsites, referenced from a master. A bare bones implementation looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
{-# LANGUAGE QuasiQuotes, TypeFamilies, MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell, FlexibleInstances, OverloadedStrings #-}
import Yesod

data SubSite1 = SubSite1
data SubSite2 = SubSite2

getSub1RootR :: Yesod master => GHandler SubSite1 master RepHtml
getSub1RootR = defaultLayout [whamlet|Welcome to subsite 1!|]

getSub2RootR :: Yesod master => GHandler SubSite2 master RepHtml
getSub2RootR = defaultLayout [whamlet|Welcome to subsite 2!|]

mkYesodSub "SubSite1" [] [parseRoutes|
/ Sub1RootR GET
|]

mkYesodSub "SubSite2" [] [parseRoutes|
/ Sub2RootR GET
|]

data Master = Master
    {
      getSub1 :: SubSite1
    , getSub2 :: SubSite2
    }

mkYesod "Master" [parseRoutes|
/ RootR GET
/sub1 SubSite1R SubSite1 getSub1
/sub2 SubSite2R SubSite2 getSub2
|]

instance Yesod Master

getRootR :: GHandler sub Master RepHtml
getRootR = defaultLayout [whamlet|
<h1>Welcome to the master page
<p>
    <a href=@{SubSite1R Sub1RootR}>sub1
<p>
    <a href=@{SubSite2R Sub2RootR}>sub2
|]

main = warpDebug 3000 $ Master SubSite1 SubSite2

Were this post to end here, it would have been even weaker than it is. However, there is an anecdote attached to it that illustrates the convenience of the strong static typing of Haskell, and this is the real intention of the post: at first, I only added /sub2 SubSite2R SubSite2 getSub2 in the parseroutes argument for mkYesod (line 28), but failed to add SubSite2 to the data constructor invocation for Master in main (line 45). In my rushed state I had mistakenly read that one data constructor invocation as two separate type constructor invocations for the data types and consequently as theirs being individual arguments to warpDebug — which made me think I couldn't possibly reference the other subsite there. Now, I compiled to see what I got so I could figure out where to stick a reference to the second subsite. Guess what? Compilation error! And it kindly told me there was stuff missing in the data constructor call... I immediately realised what a half-wit I was, and that that Master call was a data constructor. Having corrected the constructor invocation I compiled again and it worked. In a language with weaker typing it would probably have run, missing one route, and leaving me flabbergasted for yet some time.

This is admittedly a silly mistake even by my standards, but silly mistakes happen all the time in programming. Here at least, I am relieved to say, I had a friend in the compiler protecting me against my own idiocy. I will cry a deluge the day I have to go back using weakly typed languages...