martes, 5 de enero de 2016

Hamlet and Plain Haskell

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:

  1. Shakespearean templates: These is a template haskell solution provided by the shakespeare package.
  2. Plain haskell: I have written a package named yesod-bootstrap. This is similar in spirit to lucid. However, it uses yesod's WidgetT instead of lucid's HtmlT.

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:

  1. The navbar function takes a list of NavbarItems as an argument. In hamlet, we couldn't split this up onto multiple lines.
  2. The container function would have the same problem we discussed earlier. We could just write <div.container> instead.
  3. The panelAccordion function takes a list of Panel site. These contain Widgets. 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.