[Haskell-cafe] Templates as typeclasses?

oleg at okmij.org oleg at okmij.org
Fri May 9 10:55:34 UTC 2014

Tobias Dammers wrote:
> You can do similar things with Haskell EDSL-style template systems such
> as Blaze...
> Still, the big picture with these is that template building blocks are
> language elements, and template code and regular code can be mixed
> freely. The `h1` function from Blaze is just another Haskell function;
> it takes a MarkdownM () value and returns a MarkdownM (), and because
> MarkdownM happens to be a Monad, you can use do notation to sequence
> HTML elements. MarkdownM () is also an instance of IsString, so if you
> enable -XOverloadedStrings, you can use string literals to produce HTML
> text nodes. This means that you can write, in plain Haskell:
>     h1 $ do
>         span "Hello, "
>         span "world!"
> ...and it'll produce <h1><span>Hello, </span><span>world!</span></h1>.

In this regard it is worth mentioning HSXML

The document in HSXML is a monoid rather than a monad. It is
hard to find a good reason for the document be a monad except for
using a do notation, which is not needed in HSXML. The above "Hello,
World!" example looks in HSXML as

        h1 (span "Hello") (span "world!")

(Note that we don't need the space after `Hello'.) This produces the
desired output (in plain text, HTML or XML). If we don't need span, we
can just write
        h1 "Hello" "world!"
The main difference from Blaze shows up if we attempt
        h1 (p "Hello") "world!"

It will be a type error. The error message says
    No instance for (Build (DC CT_inline d0) (DC CT_block d0) t)
     arising from a use of `h1'
    No instance for (Build
                       (DC CT_inline d0) (DC CT_block d0) (DC CT_inline d0))
      arising from a use of `p'

That is, (p "Hello") produces block-level content and h1 requires
its children to be inline-level. HSXML thus implements the
block/inline content model of HTML. Arbitrary many content
content-level tags may be defined (in fact, HSXML, besides
block/inline elements uses document-, attribute-, and table-level
elements). It is possible for the same element to be usable in
different contexts (e.g., 'title' can be either an attribute or
an element used within 'head'). It is rendered differently in
different contexts.

Although the document itself is just a monoid rather than a monad,
rendering a document can be in a monad. For instance, one of the
rendering engines works in an IO monad so it can validate local links
as the document is rendered. The resulting document does not have dead
local links.

> For the common case where you have a "master" template and an
> incarnation, you could write a master template that takes its inner
> blocks as arguments, e.g.:

It is very easy to do that in HSXML. Here is an example, the top
template for the ChangeLog:

toHTML :: MDoc d => CLHead d -> DC CT_root d
toHTML (CLHead attrs updates) = 
     (title (cdata (ha_title attrs)))
     (meta_tag (description (cdata (ha_description attrs))))
     (meta_tag (pub_date (ha_DateRevision attrs)))
     (head_link LR_top (href (ha_top attrs)) (title "All you can find here"))
   (h1 "Log of changes on" (aref (ha_top attrs) "this site"))
   (p nbsp)
   (change_log_prev (ha_history_first attrs))
   (change_log_prev (ha_history_last attrs))

(One could omit many parentheses using $, but I like parentheses in
this context).

More information about the Haskell-Cafe mailing list