[Haskell-cafe] Templates as typeclasses?

Tobias Dammers tdammers at gmail.com
Fri May 9 08:15:38 UTC 2014


You can do similar things with Haskell EDSL-style template systems such
as Blaze, except that Haskell is not an OOP language, so you will be
using different abstractions instead.

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>.

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.:

    masterT :: Text -> Html -> Html -> Html
    masterT titleText headerExtra content = do
        html $ do
            header $ do
                title $ toHtml titleText
                headerExtra
            body $ do
                h1 $ toHtml titleText
                div ! class_ "content" $ content
                footer $ "This is my super footer!"

If you want something that behaves more like inheritance, the best way
IMO would be to define the available blocks as a record type, e.g.:

    data TemplateBlocks =
        TemplateBlocks
            { tbTitleText :: Text
            , tbHeaderExtra :: Html
            , tbContent :: Html
            }
    -- Then provide some sensible defaults:
    defTemplateBlocks :: TemplateBlocks
    defTemplateBlocks =
        TemplateBlocks
            { tbTitleText = "My super web app"
            , tbHeaderExtra = return ()
            , tbContent = return ()
            }
    -- and then your master template becomes:
    masterT :: TemplateBlocks -> Html
    masterT tb = do
        html $ do
            header $ do
                title $ toHtml $ tbTitleText tb
                tbHeaderExtra tb
            body $ do
                h1 $ toHtml $ tbTitleText tb
                div ! class_ "content" $ tbContent tb
                footer $ "This is my super footer!"
    -- And then you can call it with the defaults, overriding
    -- as needed:
    masterT $ defTemplateBlocks
                { tbTitleText = "Homepage"
                , tbContent = div "Hello, world!"
                }

Now, if you want to use template blocks *inside* template blocks, you
need to alter your blocks a tiny bit in order to read other blocks into
them:

    data TemplateBlocks =
        TemplateBlocks
            { tbTitleText :: Text
            , tbHeaderExtra :: TemplateBlocks -> Html
            , tbContent :: TemplateBlocks -> Html
            }
    -- now you could do, for example:
    masterT $ defTemplateBlocks
                { tbTitleText = "Homepage"
                , tbContent tb =
                    div $ do
                        "Hello, world!"
                        "You are here: "
                        toHtml $ tbTitleText tb
                }

Now if MarkupM were implemented as a monad transformer, we could even
stack it on top of a MonadReader TemplateBlocks to avoid passing the tb
parameter explicitly, and lensify the whole thing, but oh well. (BTW, is
anyone aware of any efforts in making Blaze into a transformer?)

On Fri, May 09, 2014 at 02:34:52AM -0500, Mike Meyer wrote:
> On Fri, May 9, 2014 at 2:11 AM, Tobias Florek <haskell at ibotty.net> wrote:
> 
> > hi,
> >
> >  The objects generated by the templates don't do much of
> >> anything to let the application author leverage the type system, which
> >> is what makes Cheetah stand out from other web template systems.
> >>
> >
> > can you elaborate on that point? i would really like to see an example on
> > what you mean.
> >
> > for another datapoint, have a look at hastache's use of generics
> >  hackage.haskell.org/package/hastache/docs/Text-Hastache.html
> 
> 
> I mentioned that briefly in the original message: A Cheetah template is a
> Python class. It can inherit from Python classes, and Python classes can
> inherit from it.
> 
> A standard usage is to have a page template that sets up headers and
> footers, and make your pages are all children of that class. While
> header/footer support is a stock feature of any modern templating system,
> Cheetah does it by leveraging it's integration into the Python class system
> instead of providing a specific mechanism for it.
> 
> If you need something to happen on the server when the page renders - that
> doesn't leave any traces in the output - you can subclass the template with
> a standard Python class and provide that functionality. You then use the
> Python subclass just like a regular template.  Again, the behavior isn't
> special, but doing it takes no special machinery, just leveraging Cheetah
> templates being classes.
> 
> You can find simple examples of this in the Cheetah docs:
> http://www.cheetahtemplate.org/docs/users_guide_html_multipage/howWorks.objoriented.html

> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe at haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe



More information about the Haskell-Cafe mailing list