Hamlet and Plain Haskell
A coworker of mine just asked about my preference for generating markup
in haskell web applications. I figured I would write up my response as
a blog post. We are working on a yesod
web application,
so understand that this discussion takes place within that context. We
are using WidgetT
(yesod
's html/css/javascript builder type).
There are two options we have:
- Shakespearean templates: These is a template haskell solution provided
by the
shakespeare
package. - Plain haskell: I have written a package named
yesod-bootstrap
. This is similar in spirit tolucid
. However, it usesyesod
'sWidgetT
instead oflucid
'sHtmlT
.
Let's look at the difference between the two for a simple piece of html that uses several bootstrap classes and components. With hamlet, we can write:
<div.container>
<div.col-md-9>
<h1>Your page
<p>This is the body copy
<div.col-md-3>
<h3>Sidebar
<p>The sidebar copy goes here
Now we will do the same with plain haskell. It's worthing mentioning
that WidgetT site IO ()
has an IsString
instance. Also, we are
assuming that Yesod.Bootstrap
has been imported.
div_ [("class","container")] $ do
div_ [("class","col-md-9")] $ do
h1_ [] "Your page"
p_ [] "This is the body copy"
div_ [("class","col-md-3")] $ do
h3_ [] "Sidebar"
p_ [] "The sidebar copy goes here"
The two look remarkably similar. The main difference is that the
plain haskell version is more verbose. Although, it is worth
mentioning that by using some of the combinators in
Yesod.Bootstrap
, it could be rewritten as:
container $ do
col [ColSize Medium 9] $ do
h1_ [] "Your page"
p_ [] "This is the body copy"
col [ColSize Medium 3] $ do
h3_ [] "Sidebar"
p_ [] "The sidebar copy goes here"
The container
and col
functions help reduce some of the boilerplate
involved with using bootstrap. Let's look at the type signature and
definition of container
:
container :: WidgetT site IO () -> WidgetT site IO ()
container = div_ [("class","container")]
At first glance, it seems like we should be able to use container
in
a shakespearean template. After all, the ^{..}
widget interpolation
should work. We would like to be able to write:
^{container}
<p>Stuff in the container
But that doesn't work. The problem is that this construct only accepts a fully applied value. So we would have to write:
^{container stuffInContainer}
And stuffInContainer
needs to be defined elsewhere. So doing this is
possible using shakespearean templates, but this is a cumbersome way
to produce markup.
To be clear, this is the drawback of shakespearean templates: if you
use a Widget
-producing function that accepts at least one other
Widget
as an argument, you can't keep all the markup in one place.
In the original example, we didn't really feel any pain from this
though. Let's turn to a more complicated example:
navbar NavbarDefault NavbarStaticTop HomeR "My Site"
[ NavbarLink CustomersR $ "Customers " >> glyphicon "scale"
, NavbarLink LearningR $ "Learn" >> glyphicon "education"
, NavbarLink ContactR "Contact Us"
] []
container $ do
h1_ [] "My Site Landing"
p_ [] $ do
"Welcome to this site. The features of this site are in "
"the panel accordion below:"
panelAccordion
[ basicPanel "Lower Prices" $ do
p_ [] $ "We have the " >> strong_ [] "lowest" >> "prices."
, basicPanel "Best Features" $ do
p_ [] $ "Our features are " >> em_ [] "outstanding" >> "."
, basicPanel "Great Staff" $ do
p_ [] $ do
"Come in person or "
anchor ContactR "contact us online"
"."
]
I'm not going to attempt to recreate this using hamlet, but I'll point out what would be different:
- The
navbar
function takes a list ofNavbarItem
s as an argument. In hamlet, we couldn't split this up onto multiple lines. - The
container
function would have the same problem we discussed earlier. We could just write<div.container>
instead. - The
panelAccordion
function takes a list ofPanel site
. These containWidget
s. We would encounter the same problem that we did when translating the navbar. We would need to declare it outside of the template itself. Both of these options hurt the readability of the template.
To conclude, I'm not against shakespearean templates in all situations. When I was first learning haskell, it was an immensely helpful way to deal with markup. As I started writing combinators to factor out common bootstrap idioms, I backed away from hamlet because I kept finding myself in situations where I had to split up template that were really more readable when everything was together. Now I prefer writing plain haskell to hamlet.