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

Michael Snoyman michael at snoyman.com
Mon Sep 24 07:20:32 CEST 2012


On Sun, Sep 23, 2012 at 10:15 PM, Jeremy Shaw <jeremy at n-heptane.com> wrote:
> On Sun, Sep 23, 2012 at 7:02 AM, Michael Snoyman <michael at snoyman.com> wrote:
>> On Sun, Sep 23, 2012 at 11:14 AM, Sebastian Fischer <mail at sebfisch.de> wrote:
>
>> The most basic reason is because I think multiple syntaxes is a good
>> thing. Embedding a URL is inherently a different activity than
>> embedding a piece of text, and I'd like the code to reflect that. In
>> Hamlet, the following is clear:
>>
>>     <a href=@{foo}>#{bar}
>>
>> I know that foo is a URL value, and bar is not. If I accidentally
>> included the wrong type for `foo`, Hamlet can catch it.
>
> But that doesn't prevent you from making these mistakes, right?
>
>  <a href=#{bar}>
>  <a class=@{foo}>

Of course not. There are times when something like that is completely valid:

<img src=@{foo} title=@{foo}>

let bar = "http://www.google.com/"
<a href=#{bar}>

But it makes the common case look correct, and the exceptional cases
look exceptional, which definitely helps with catching mistakes.

> It seems like we could achieve a similar effect in HSP by using a
> newtype wrapper?
>
> newtype At url = At url
>
> And then having our EmbedAsAttr look like:
>
>> instance (XMLGenerator m, MonadRoute m, URL m ~ url) => EmbedAsAttr m (Attr key (At url)) where
>
> So that you now have to write:
>
>  <a href=(At Home)>home</a>
>
> And the following would be rejected:
>
>  <a href=Home>home</a>
>  <a href=(At NotAUrl)>not a url</a>
>  <a><% Home %></a>
>  <a><% (At Home) %></a>
>
> Though, the following would still be accepted:
>
>  <a class=(At Home)>oops</a>
>  <a href=NotAURL>
>
>> I actually contacted Jeremy off list about this issue to get a better
>> understanding of how HXT works, and it seems to me that it's pushing a
>> lot of the infrastructure into a monad with typeclass instances. I
>> don't know all the details, so I can't really speak to that approach.
>> But I *can* speak to how Hamlet was designed, and I think the fact
>> that context is irrelevant is very powerful. Each Hamlet template is
>> passed a URL rendering function, which ensures automatically that all
>> embedded URLs are of the correct type.
>>
>> This is especially useful for cases such as subsites. Suppose that you
>> have a subsite for static files, and you want to give a user a link to
>> an image. You could construct such a static route along the lines of:
>>
>>     StaticRoute ["myimage.png"]
>>
>> (Yesod users will probably recognize that these links are
>> automatically generated, so you could just use the `myimage_png`
>> identifier, which ensures you do not have any typos.)
>>
>> The question is: what does `myimage_png` render to? And the answer
>> depends entirely on your application. There are a number of
>> possibilities:
>>
>> 1. You stuck your static subsite at the "standard" location of
>> /static, so it renders to /static/myimage_png
>> 2. You used a nonstandard location: /foo
>> 3. You don't even have a static subsite set up
>> 4. You have multiple static subsites set up
>>
>> In any event, trying to use `<img src=@{myimage_png}>` will fail in
>> *all* of these cases with a message along the lines of "Route Static
>> does not match Route App" which, if you're familiar with GHC error
>> messages, means that you tried to use a route for the static subsite
>> when you should have used a route for the application. The only way to
>> create the link is to wrap up `myimage_png` in the appropriate
>> constructor, which in common nomenclature would work out to `<img
>> src=@{StaticR myimage_png}>`.
>
> If I understand you correctly, you have a type like:
>
> data Static = StaticRoute [FilePath]
>
> And also a route like:
>
> data App = StaticR StaticRoute
>
> And you are saying that you can not do:
>
>  <img src=@{StaticRoute ["myimage.png"]> in a template that requires
> the App route? you instead need:
>
> <img src=@{StaticR (StaticRoute ["myimage.png"])}> ?
>
> That would be exactly the same as HSP. Assuming we have something like:
>
> type MyApp url a = ...
>
> if you have:
>
> foo :: MyApp App XML
> foo = <a href=(StaticRoute ["myimage.png"])>my image</a>
>
> It would fail. You would need:
>
> foo :: MyApp App XML
> foo = <a href=(StaticR $ StaticRoute ["myimage.png"])>my image</a>
>
> If you already had a template like:
>
> foo :: MyApp Static XML
> foo = <a href=(StaticRoute ["myimage.png"])>my image</a>
>
> and you want to use it in 'MyApp App XML' template, you should be able
> to wrap it in StaticR using: 'nestURL StaticR'
>
> In Hamlet you explicitly pass in a url rendering function. In the
> HSP+web-routes stuff we also pass in a url rendering function. The
> primary difference, I think, is that in hamlet it is an explicit
> argument to the template and in HSP+web-routes we put the render
> function in a ReaderT monad?
>
> - jeremy

As an aside, Yesod has the concept of a Widget which works very
similarly to how you describe HSP+web-routes. I avoided bringing it up
so far so as not to confuse the topic, but the concept of embedding
the rendering function inside the monad exists in the Hamlet/Yesod
world as well.

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.

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. (Note: These questions likely speak
more to my ignorance of things than to anything else, I'm just trying
to understand how HSP gets these features.)

Michael



More information about the web-devel mailing list