O'Reilly logo

Developing Web Applications with Haskell and Yesod by Michael Snoyman

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Syntax

All Shakespearean languages share the same interpolation syntax, and are able to utilize type-safe URLs. They differ in the syntax specific for their target language (HTML, CSS, or JavaScript).

Hamlet Syntax

Hamlet is the most sophisticated of the languages. Not only does it provide syntax for generating HTML, it also allows for basic control structures: conditionals, looping, and maybes.

Tags

Obviously tags will play an important part of any HTML template language. In Hamlet, we try to stick very close to existing HTML syntax to make the language more comfortable. However, instead of using closing tags to denote nesting, we use indentation. So something like this in HTML:

<body>
<p>Some paragraph.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</body>

would be

<body>
    <p>Some paragraph.
    <ul>
        <li>Item 1
        <li>Item 2

In general, we find this to be easier to follow than HTML once you get accustomed to it. The only tricky part comes with dealing with whitespace before and after tags. For example, let’s say you want to create the HTML

<p>Paragraph <i>italic</i> end.</p>

We want to make sure that there is a whitespace preserved after the word “Paragraph” and before the word “end”. To do so, we use two simple escape characters:

<p>
    Paragraph #
    <i>italic
    \ end.

The whitespace escape rules are actually very simple:

  1. If the first non-space character in a line is a backslash, the backslash is ignored.

  2. If the last character in a line is a hash, it is ignored.

One other thing. Hamlet does not escape entities within its content. This is done on purpose to allow existing HTML to be more easily copied in. So the example above could also be written as:

<p>Paragraph <i>italic</i> end.

Notice that the first tag will be automatically closed by Hamlet, while the inner “i” tag will not. You are free to use whichever approach you want, there is no penalty for either choice. Be aware, however, that the only time you use closing tags in Hamlet is for such inline tags; normal tags are not closed.

Interpolation

What we have so far is a nice, simplified HTML, but it doesn’t let us interact with our Haskell code at all. How do we pass in variables? Simple: with interpolation:

<head>
    <title>#{title}

The hash followed by a pair of braces denotes variable interpolation. In the case above, the title variable from the scope in which the template was called will be used. Let me state that again: Hamlet automatically has access to the variables in scope when it’s called. There is no need to specifically pass variables in.

You can apply functions within an interpolation. You can use string and numeric literals in an interpolation. You can use qualified modules. Both parentheses and the dollar sign can be used to group statements together. And at the end, the toHtml function is applied to the result, meaning any instance of ToHtml can be interpolated. Take, for instance, the following code.

-- Just ignore the quasiquote stuff for now, and that shamlet thing.
-- It will be explained later.
{-# LANGUAGE QuasiQuotes #-}
import Text.Hamlet (shamlet)
import Text.Blaze.Renderer.String (renderHtml)
import Data.Char (toLower)
import Data.List (sort)

data Person = Person
    { name :: String
    , age  :: Int
    }

main :: IO ()
main = putStrLn $ renderHtml [shamlet|
<p>Hello, my name is #{name person} and I am #{show $ age person}.
<p>
    Let's do some funny stuff with my name: #
    <b>#{sort $ map toLower (name person)}
<p>Oh, and in 5 years I'll be #{show (5 + (age person))} years old.
|]
  where
    person = Person "Michael" 26

What about our much-touted type-safe URLs? They are almost identical to variable interpolation in every way, except they start with an at-sign (@) instead. In addition, there is embedding via a caret (^) which allows you to embed another template of the same type. The next code sample demonstrates both of these.

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import Text.Hamlet (HtmlUrl, hamlet)
import Text.Blaze.Renderer.String (renderHtml)
import Data.Text (Text)

data MyRoute = Home

render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"

footer :: HtmlUrl MyRoute
footer = [hamlet|
<footer>
    Return to #
    <a href=@{Home}>Homepage
    .
|]

main :: IO ()
main = putStrLn $ renderHtml $ [hamlet|
<body>
    <p>This is my page.
    ^{footer}
|] render

Attributes

In that last example, we put an href attribute on the “a” tag. Let’s elaborate on the syntax:

  • You can have interpolations within the attribute value.

  • The equals sign and value for an attribute are optional, just like in HTML. So <input type=checkbox checked> is perfectly valid.

  • There are two convenience attributes: for id, you can use the hash, and for classes, the period. In other words, <p #paragraphid .class1 .class2>.

  • While quotes around the attribute value are optional, they are required if you want to embed spaces.

  • You can add an attribute optionally by using colons. To make a checkbox only checked if the variable isChecked is True, you would write <input type=checkbox :isChecked:checked>. To have a paragraph be optionally red, you could use <p :isRed:style="color:red">.

Conditionals

Eventually, you’ll want to put some logic in your page. The goal of Hamlet is to make the logic as minimalistic as possible, pushing the heavy lifting into Haskell. As such, our logical statements are very basic…so basic, that it’s if, elseif, and else.

$if isAdmin
    <p>Welcome to the admin section.
$elseif isLoggedIn
    <p>You are not the administrator.
$else
    <p>I don't know who you are. Please log in so I can decide if you get access.

All the same rules of normal interpolation apply to the content of the conditionals.

Maybe

Similarly, we have a special construct for dealing with Maybe values. This could technically be dealt with using if, isJust, and fromJust, but this is more convenient and avoids partial functions.

$maybe name <- maybeName
    <p>Your name is #{name}
$nothing
    <p>I don't know your name.

In addition to simple identifiers, you can use a few other, more complicated values on the left-hand side, such as constructors and tuples.

$maybe Person firstName lastName <- maybePerson
    <p>Your name is #{firstName} #{lastName}

The right-hand side follows the same rules as interpolations, allow variables, function application, and so on.

Forall

And what about looping over lists? We have you covered there too:

$if null people
    <p>No people.
$else
    <ul>
        $forall person <- people
            <li>#{person}

Case

Pattern matching is one of the great strengths of Haskell. Sum types let you cleanly model many real-world types, and case statements let you safely match, letting the compiler warn you if you missed a case. Hamlet gives you the same power.

$case foo
    $of Left bar
        <p>It was left: #{bar}
    $of Right baz
        <p>It was right: #{baz}

With

Rounding out our statements, we have with. It’s basically just a convenience for declaring a synonym for a long expression.

$with foo <- some very (long ugly) expression that $ should only $ happen once
    <p>But I'm going to use #{foo} multiple times. #{foo}

Doctype

Last bit of syntactic sugar: the doctype statement. We have support for a number of different versions of a doctype, though we recommend $doctype 5 for modern web applications, which generates <!DOCTYPE html>.

$doctype 5
<html>
    <head>
        <title>Hamlet is Awesome
    <body>
        <p>All done.

Note

There is an older and still supported syntax: three exclamation points (!!!). You may still see this in code out there. We have no plans to remove support for this, but in general find the $doctype approach easier to read.

Cassius Syntax

Cassius is the original CSS template language. It uses simple whitespace rules to delimit blocks, making braces and semicolons unnecessary. It supports both variable and URL interpolation, but not embedding. The syntax is very straightforward:

#banner
    border: 1px solid #{bannerColor}
    background-image: url(@{BannerImageR})

Lucius Syntax

While Cassius uses a modified, whitespace-sensitive syntax for CSS, Lucius is true to the original. You can take any CSS file out there and it will be a valid Lucius file. There are, however, a few additions to Lucius:

  • Like Cassius, we allow both variable and URL interpolation.

  • CSS blocks are allowed to nest.

  • You can declare variables in your templates.

Starting with the second point: let’s say you want to have some special styling for some tags within your article. In plain ol’ CSS, you’d have to write:

article code { background-color: grey; }
article p { text-indent: 2em; }
article a { text-decoration: none; }

In this case, there aren’t that many clauses, but having to type out article each time is still a bit of a nuisance. Imagine if you had a dozen or so of these. Not the worst thing in the world, but a bit of an annoyance. Lucius helps you out here:

article {
    code { background-color: grey; }
    p { text-indent: 2em; }
    a { text-decoration: none; }
}

Having Lucius variables allows you to avoid repeating yourself. A simple example would be to define a commonly used color:

@textcolor: #ccc; /* just because we hate our users */
body { color: #{textcolor} }
a:link, a:visited { color: #{textcolor} }

Other than that, Lucius is identical to CSS.

Julius Syntax

Julius is the simplest of the languages discussed here. In fact, some might even say it’s really just JavaScript. Julius allows the three forms of interpolation we’ve mentioned so far, and otherwise applies no transformations to your content.

Note

If you use Julius with the scaffolded Yesod site, you may notice that your JavaScript is automatically minified. This is not a feature of Julius; instead, Yesod uses the hjsmin package to minify Julius output.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required