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

Michael Snoyman michael at snoyman.com
Thu Sep 27 00:59:12 CEST 2012


On Mon, Sep 24, 2012 at 4:43 PM, Jeremy Shaw <jeremy at n-heptane.com> wrote:
> 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:
>
> ../haskell/playground/url-errors.hs:36:40:
>     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 =
>     <div>
>      <a href=Home>home</a>
>      <% nestXURL SS subTemplate %>
>     </div>
>
> 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.

I think I've got it now. Thanks for the clear explanations!

Michael



More information about the web-devel mailing list