[web-devel] Uniform substitution for URLs, templates, messages and other expressions in Shakespearean templates

Jeremy Shaw jeremy at n-heptane.com
Mon Sep 24 16:43:23 CEST 2012

On Mon, Sep 24, 2012 at 12:20 AM, Michael Snoyman <michael at snoyman.com> wrote:

> What I'm confused about is this. There's no distinction between URL
> and other kinds of embedding. So when I see:
>     <a href=foo>
> I don't know if the variable `foo` is supposed to be a URL or some
> other instance of `EmbedAsAttr`. So how is it possible for you to
> statically disallow as `StaticRoute` from being used? I understand
> that it will fail because there is no instance of `EmbedAsAttr`
> available, but the error message will say that there is an instance
> missing, as opposed to in Hamlet where it will say that the wrong
> route is being used. My point being, that as I understand it, a
> sufficiently determined and/or confused user could create such an
> instance.

Correct, they will get a missing instance error. A sufficiently
determined user could try to create an instance. To do that, they need
some way to turn a SubSite into a attribute value.

One option is, of course, to convert the SubSite into a Route. So if
they came up with:

instance (XMLGenerator m, EmbedAsAttr m (Attr String Route)) =>
EmbedAsAttr m (Attr String SubSite) where
   asAttr (k := ss) = asAttr (k := (SS ss))

then this would actually work:

badTemplate :: XMLGenT (RouteT Route (ServerPartT IO)) XML
badTemplate =
    <a href=SubSite>home</a>

And do the right thing. If instead they decide to just call 'show' on
the type and embed that.. then they must be exceedingly confused.

As an alternative we could provide an 'at' function like:

at :: (MonadRoute m, URL m ~ url) => url -> XMLGenT m Text
at = showURL

And now when you write:

badTemplate :: XMLGenT (RouteT Route (ServerPartT IO)) XML
badTemplate =
    <a href=(at SubSite)>home</a>

You get the error:

    Couldn't match type `Route' with `SubSite'
    In the second argument of `(:=)', namely `(at SubSite)'
    In the first argument of `asAttr', namely
      `(("href" :: String) := (at SubSite))'
    In the expression: asAttr (("href" :: String) := (at SubSite))

So now we get the the two things you mentioned:

 1. values that are supposed to be embedded as a URL look different visually
 2. the error message is more explicit about where you went wrong

> I'm also not certain how your nestURL works, for similar reasons. In
> Hamlet, the idea would be to pass in a modified rendering function,
> but again I don't see how it's possible to do that for HSP when you
> don't know which interpolations are arbitrary EmbedAsAttr values and
> which are specifically routes.

A bit like this:

data SubSite = SubSite
           deriving (Eq, Ord, Read, Show)
$(derivePathInfo ''SubSite)

data Route
    = Home
    | SS SubSite
           deriving (Eq, Ord, Read, Show)
$(derivePathInfo ''Route)

-- this function is stupid boilerplate we have not gotten around to
eliminating properly
nestXURL :: (url1 -> url2) -> XMLGenT (RouteT url1 m) a -> XMLGenT
(RouteT url2 m) a
nestXURL f = mapXMLGenT (nestURL f)

subTemplate :: XMLGenT (RouteT SubSite (ServerPartT IO)) XML
subTemplate =
    <a href=SubSite>A sub-link link in a sub-site template</a>

mainTemplate :: XMLGenT (RouteT Route (ServerPartT IO)) XML
mainTemplate =
     <a href=Home>home</a>
     <% nestXURL SS subTemplate %>

Note that in practice we typically have type or newtype aliases to
make the type signatures more readable. But I left everything expanded
here so that it is easier to understand what is going on.

- jeremy

More information about the web-devel mailing list