I’m not big on custom templating languages, for reasons I’ll write about another time. I prefer EDSLs. I preferred the xhtml package back when that was what everybody used. It looked like this:
<< thetitle << "Page title"
header
! [theclass "logo"] << "…"
thediv noHtml ! [identifier "login"] thediv noHtml
Pretty line-noisy to read, write and hard to edit in a reasonable manner.
Later, blaze-html became the new goto HTML writing library. It improved upon the XHTML package by being faster and having a convenient monad instance. It looks like this:
= html $ do
page1 head $ do
"Introduction page."
title ! rel "stylesheet" ! type_ "text/css" ! href "screen.css"
link $ do
body div ! id "header" $ "Syntax"
"This is an example of BlazeMarkup syntax."
p $ mapM_ (li . toMarkup . show) [1, 2, 3] ul
Much easier to read, write and edit thanks to the monad instance.
However, after several years of using that, I’ve come to write my own. I’ll cover the infelicities about Blaze and then discuss my alternative approach.
Reading back through what I’ve written below, it could be read as a bit attacky, and some of the issues are less philosophical and more incidental. I think of it more that the work on writing HTML in a DSL is incomplete and to some degree people somewhat gave up on doing it more conveniently at some point. So I’m re-igniting that.
The combination of having a need to write a few HTML reports and recent discussions about Blaze made me realise it was time for me to come at this problem a-fresh with my own tastes in mind. I also haven’t used my own approach much, other than porting some trivial apps to it.
The first problem is that Blaze exports many names which conflict with base. Examples:
div
, id
, head
,
map
The obvious problem with this is that you either have to qualify any use of those names, which means you have to qualify Blaze, and end up with something inconsistent like this:
! A.id "logo" $ "…" H.div
Where H
and A
come from importing the
element and attribute modules like this:
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
Or you don’t import Prelude
and only import Blaze, but
then you can’t do a simple map
without qualification.
You might’ve noticed in the old xhtml
package that
thediv
and identifier
are used instead. The
problem with using different names from the actual things they refer to
is that they’re hard to learn and remember, both for regular Haskellers
and newbies coming to edit your templates.
This is a common problem in DSLs, too. In Blaze the problem is:
class
or type
(perhaps others I don’t recall).
Blaze solves it with: class_
or type_
Again, the problem with this is that it is inconsistent with the other naming conventions. It’s another exception to the rule that you have to remember and makes the code look bad.
There are also names which are used for both attributes and elements.
Examples are style
and map
. That means you
can’t write:
$ style "body { background: red; }"
H.head $ p ! style "foo" $ … body
You end up writing:
$ H.style "body { background: red; }"
H.head $ p ! A.style "foo" $ … body
What the above problems amount to is ending up with code like this:
$ H.div ! A.id "logo" ! class_ "left" ! hidden $ "Content" body
At this point users of Blaze give up with second-guessing every markup term they write and decide it’s more consistent to qualify everything:
$ H.div ! A.id "logo" ! A.class_ "left" ! A.hidden $ "Content" H.body
Or, taken from some real code online:
H.! A.type_ "checkbox"
H.input H.! A.checked True
H.! A.readonly "true"
This ends up being too much. Inconvenient to type, ugly to read, and one more step removed from the HTML we’re supposed to be generating.
The monad instance was originally conceived as a handy way to write
HTML nicely without having to use <>
or lists of
lists and other less wieldy syntax.
In the end the monad ended up being defined like this:
instance Monad MarkupM where
return _ = Empty
{-# INLINE return #-}
>>) = Append
({-# INLINE (>>) #-}
>>= f = h1 >> f
h1 error "Text.Blaze.Internal.MarkupM: invalid use of monadic bind")
({-# INLINE (>>=) #-}
And has been for some years. Let’s take a trivial example of why this is not good. You render some HTML and while doing so build a result to be used later:
do xs <- foldM (\c i -> …)
mempty
ysmapM_ dd xs
Uh-oh:
*** Exception: Text.Blaze.Internal.MarkupM: invalid use of monadic bind
The previous point leads onto this next point, which is that due to this phantomesque monad type, the instance is like this:
instance IsString (MarkupM a) where
= Content . fromString
fromString {-# INLINE fromString #-}
How can it make this value? It cannot. If you want to go ahead and extract that `a’, you get:
*** Exception: Text.Blaze.Internal.MarkupM: invalid use of monadic bind
Additionally, this instance is too liberal. You end up getting this warning:
A do-notation statement discarded a result of type
GHC.Prim.Any
Suppress this warning by saying
_ <- "Example"
or by using the flag-fno-warn-unused-do-bind
So you end up having to write in practice (again, taken from a real Blaze codebase by one of the authors):
void "Hello!"
Which pretty much negates the point of using IsString
in
the first-place. Alternatively, you use
-fno-warn-unused-do-bind
in your module.
The ! syntax seems pretty convenient from superficial inspection:
! rel "stylesheet" ! type_ "text/css" ! href "screen.css" link
But in practice it means you always have the same combination:
div ! H.class_ "logo" $ "…"
Which I find—personally speaking—a bit distasteful to read, it’s not far from what we saw in the old xhtml package:
! [theclass "logo"] << "…" thediv
Did we really save that much in the attribute department? Operators are evil.
But mostly presents an editing challenge. Operators like this make it tricky to navigate, format in a regular way and do code transformations on. All Haskell code has operators, so this is a general problem. But if your DSL doesn’t actually need these operators, I consider this a smell.
You should be able to compose with
. For example, let’s
say you want to define a re-usable component with bootstrap:
= div ! class_ "container" $ inner container inner
Now you can use it to make a container. But consider now that you also want to add additional attributes to it later. You can do that with another call to with:
! class_ "main" $ "zot" container
In Blaze this produces:
> main
λ"<div class=\"container\" class=\"main\">My content!</div>"
Browsers ignore the latter main, so the composition didn’t work.
Here’s the example from Blaze’s package, that’s introduced to users.
import Prelude hiding (head, id, div)
import Text.Blaze.Html4.Strict hiding (map)
import Text.Blaze.Html4.Strict.Attributes hiding (title)
import Text.Blaze.Renderer.Utf8 (renderMarkup)
page1 :: Markup
= html $ do
page1 head $ do
"Introduction page."
title ! rel "stylesheet" ! type_ "text/css" ! href "screen.css"
link $ do
body div ! id "header" $ "Syntax"
"This is an example of BlazeMarkup syntax."
p $ mapM_ (li . toMarkup . show) [1, 2, 3]
ul
= print (renderMarkup page1) main
Apart from the import backflips you have to do to resolve names properly, you have at least three imports to make just to render some HTML. Call me lazy, or stupid, but I never remember this deep hierarchy of modules and always have to look it up every single time. And I’ve been using Blaze for as long as the authors have.
A smaller complaint is that it would sometimes be nice to transform over another monad. Simplest example is storing the read-only model information in a reader monad and then you don’t have to pass around a bunch of things as arguments to all your view functions. I’m a big fan of function arguments for explicit state, but not so much if it’s the same argument every time.
It would be nice if you could just write some markup in the REPL without having to import some other modules and wrap it all in a function just to see it.
My new library, Lucid, attempts to solve most of these problems.
Firstly, all names which are representations of HTML terms
are suffixed with an underscore _
:
p_, class_, table_, style_
No ifs or buts. All markup terms.
That solves the following problems (from the issues described above):
div_
, id_
,
head_
, map_
, etc.class_
, type_
,
etc.style_
to mean either the
element name or the attribute name.No import problems or qualification. Just write code without worrying about it.
Plain text is written using the OverloadedStrings
and
ExtendedDefaultRules
extensions, and is automatically
escaped:
> "123 < 456" :: Html ()
λ123 < 456
Elements nest by function application:
> table_ (tr_ (td_ (p_ "Hello, World!")))
λ<table><tr><td><p>Hello, World!</p></td></tr></table>
Elements are juxtaposed via monoidal append:
> p_ "hello" <> p_ "sup"
λ<p>hello</p><p>sup</p>
Or monadic sequencing:
> div_ (do p_ "hello"; p_ "sup")
λ<div><p>hello</p><p>sup</p></div>
Attributes are set using the with combinator:
> with p_ [class_ "brand"] "Lucid Inc"
λ<p class="brand">Lucid Inc</p>
Conflicting attributes (like style_
) work for attributes
or elements:
> html_ (head_ (style_ "body{background:red}") <>
λ"color:white"]
with body_ [style_ "Look ma, no qualification!")
<html><head><style>body{background:red}</style></head>
<body style="color:white">Look ma, no qualification!</body></html>
For comparison, here’s the Blaze example again:
= html $ do
page1 head $ do
"Introduction page."
title ! rel "stylesheet" ! type_ "text/css" ! href "screen.css"
link $ do
body div ! id "header" $ "Syntax"
"This is an example of BlazeMarkup syntax."
p $ mapM_ (li . toMarkup . show) [1, 2, 3] ul
And the same thing in Lucid:
= html_ $ do
page2 $ do
head_ "Introduction page."
title_ "stylesheet",type_ "text/css",href_ "screen.css"]
with link_ [rel_ $ do
body_ "header"] "Syntax"
with div_ [id_ "This is an example of Lucid syntax."
p_ $ mapM_ (li_ . toHtml . show) [1,2,3] ul_
I’m not into operators like ($)
and swung indentation
like that, but I followed the same format.
I’d write it in a more Lispy style and run my hindent tool on it:
=
page1 do head_ (do title_ "Introduction page."
html_ (
with link_"stylesheet"
[rel_ "text/css"
,type_ "screen.css"])
,href_ do with div_ [id_ "header"] "Syntax"
body_ ("This is an example of Lucid syntax."
p_ mapM_ (li_ . toHtml . show)
ul_ (1,2,3]))) [
But that’s another discussion.
Normal monadic operations work properly:
> (return "OK!" >>= p_)
λ<p>OK!</p>
It’s basically a writer monad.
In fact, it’s also a monad transformer:
> runReader (renderTextT (html_ (body_ (do name <- lift ask
λ
p_ (toHtml name)))))"Chris" :: String)
("<html><body><p>Chris</p></body></html>"
The instance is constrained over the return type being
()
. So string literals can only be type
HtmlT m ()
.
> do "x" >> "y" :: Html ()
λ
xy
> do x <- "x"; toHtml (show x)
λ x()
Attributes are simply written as a list. That’s all. Easy to manipulate as a data structure, easy to write and edit, and automatically indent in a predictable way:
> with p_ [id_ "person-name",class_ "attribute"] "Mary"
λ<p id="person-name" class="attribute">Mary</p>
No custom operators are required. Just the with
combinator. If you want to indent it, just indent it like normal
function application:
with p_"person-name",class_ "attribute"]
[id_ "Mary"
And you’re done.
You should be able to compose with
. For example, let’s
say you want to define a re-usable component with bootstrap:
> let container_ = with div_ [class_ "container "] λ
Now you can use it to make a container:
> container_ "My content!"
λ<div class="container ">My content!</div>
But consider now that you also want to add additional attributes to it later. You can do that with another call to with:
> with container_ [class_ "main"] "My content!"
λ<div class="container main">My content!</div>
Duplicate attributes are composed with normal monoidal append. Note that I added a space in my definition of container anticipating further extension later. Other attributes might not compose with spaces.
Another part I made sure was right was lack of import nightmare. You
just import Lucid
and away you go:
> import Lucid
λ> p_ "OK!"
λ<p>OK!</p>
> p_ (span_ (strong_ "Woot!"))
λ<p><span><strong>Woot!</strong></span></p>
> renderBS (p_ (span_ (strong_ "Woot!")))
λ"<p><span><strong>Woot!</strong></span></p>"
> renderToFile "/tmp/foo.html" (p_ (span_ (strong_ "Woot!"))) λ
If I want to do more advanced stuff, it’s all available in
Lucid
. But by default it’s absolutely trivial to get going
and output something.
Actually, despite having a trivial implementation, being a real monad and a monad transformer, it’s not far from Blaze. You can compare the benchmark reports here. A quick test of writing 38M of HTML to file yielded the same speed (about 1.5s) for both Lucid and Blaze. With such decent performance for very little work I’m already ready to start using it for real work.
So the point of this post was really to explain why another HTML DSL and I hope I did that well enough.
The code is on Github. I pushed to Hackage but you can consider it beta for now.