HSP 0.8

As of March 2020, School of Haskell has been switched to read-only mode.

HSP and hsx2hs are a pair of libraries which can be used to generate XML and HTML.

HSP 0.8 represents a major refactoring from previous versions. The essense, however, is still the same. Right now, this document just serves as a quick intro to using HSP 0.8 + hsx2hs for thoses already familiar with HSP. For more in-depth details on HSP, you can read the HSP section of the Happstack Crashcourse.

The HSP library contains types, classes, monads, and functions needed to create XML and HTML content.

The hsx2hs library contains a preprocessor and now also a QuasiQuoter which allow you to embed literal XML markup in your Haskell code. hsx2hs generates output which is compatible with HSP. While the HSP and hsx2hs libraries are designed to work in harmony, neither explicitly depends on the other. This means, for example, if you like hsx2hs, but not HSP, you can easily create your own library which is compatible with hsx2hs output.

However, here we will show how to use them together. First we will show how to use the external hsx2hs preprocessor:

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -F -pgmFhsx2hs #-}
module Main where

import           Data.Text.Lazy    (Text)
import qualified Data.Text.Lazy.IO as T
import HSP.HTML4        (renderAsHTML)
import HSP.Monad        (HSPT(unHSPT))
import HSP.XMLGenerator (Attr(..), XMLGen(..), XMLGenT, EmbedAsChild(..), EmbedAsAttr(..), unXMLGenT)
import HSP.XML          (XML)

html :: (Functor m, Monad m) => XMLGenT (HSPT XML m) XML
html = <p class="some">I haz a paragraph!</p>

main :: IO ()
main =
    do h <- unHSPT $ unXMLGenT $ html
       T.putStrLn $ renderAsHTML h

We start by enabling the OverloadedStrings pragma. HSP is now based around Text, so this will allow us to avoid having to call pack or fromString by hand for string literals.

The next line

{-# OPTIONS_GHC -F -pgmFhsx2hs #-}

tells GHC to use the hsx2hs preprocessor. Note that in prior versions the preprocessor used to be called trhsx. When migrating old code, make sure you change that or you will get very confusing error messages.

Next we have the code that actually creates some XML using literal markup:

html :: (Functor m, Monad m) => XMLGenT (HSPT XML m) XML
html = <p class="some">I haz a paragraph!</p>

The HSPT monad is essentially IdentityT with an extra phantom type parameter. The XMLGenT monad is also essentially just the IdentityT monad and exists only to make the type-checker happy.

In many applications you will not use the HSPT monad directly. Instead you will add it to your application specific monad transformer stack -- or you will ignore HSPT entirely and create an instance of XMLGen for your application specific monad.

in main we just unwrap the monads, and render the XML as HTML:

main :: IO ()
main =
    do h <- unHSPT $ unXMLGenT $ html
       T.putStrLn $ renderAsHTML h

The new hsx2hs allows us to avoid using an external preprocessor and instead use a QuasiQuoter. Here is the same app rewritten using the new hsx QuasiQuoter:

{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
module Main where

import           Data.Text.Lazy (Text)
import qualified Data.Text.Lazy.IO as T
import HSP.HTML4                   (renderAsHTML)
import HSP.Monad                   (HSPT(unHSPT))
import HSP.XML                     (XML)
import HSP.XMLGenerator            (Attr(..), XMLGenT, XMLGen(..), EmbedAsAttr(..), EmbedAsChild(..), unXMLGenT)
import Language.Haskell.HSX.QQ     (hsx)

html :: (Functor m, Monad m) => XMLGenT (HSPT XML m) XML
html = [hsx| <p class="some">I haz a paragraph!</p> |]

main :: IO ()
main =
    do h <- unHSPT $ unXMLGenT $ html
       T.putStrLn $ renderAsHTML h

We can now drop the crazy OPTIONS_GHC pragma and just add QuasiQuotes to the LANGUAGE pragma. We also import hsx from Language.Haskell.HSX.QQ.

We then use hsx in the html generation code:

html :: (Functor m, Monad m) => XMLGenT (HSPT XML m) XML
html = [hsx| <p class="some">I haz a paragraph!</p> |]

With the preprocessor, we could just put literal XML in the code with no special introduction. With the QuasiQuoter we have to explicitly delimit the XML portions.

Note that the type of the html function is the same, and that main is unaltered.

Feel free to use the preprocessor or the hsx QuasiQuoter. The preprocessor is nice because it does not require extra noise around the XML. It also outputs plain Haskell code -- which means it can be used with Fay and other compilers which do not support the QuasiQuotation extension.

However, it also strips all the comments from the code (which makes Haddock sad) and the line numbers in error messages are a bit fuzzy. It also requires that hsx2hs be in the user's path.

The QuasiQuoter version is requires a little extra noise around the XML, but generates better line numbers in error messages, leaves the Haddock comments alone, etc. Additionally, the [hsx| |] can actually make it easier for your text editor to do syntax highlighting.

Under the hood, both methods use the same code with some different glue code on top for making the transformation happen. We plan to continue supporting both methods indefinitely.