Uniform substitution for URLs, templates, messages and other expressions in Shakespearean templates

Hello, when writing my comparison of Hamlet with Heist and HXT (https://gist.github.com/3757918) I noticed that Hamlet and HXT have similar approaches to variable substitution but Hamlet's uses different syntax for substituting URLs, templates, and other expressions. I'd like to understand why. In HXT Haskell expressions can be substituted using the notation <% ... %>. The rendering of substituted expression is type based and specified separately for attribute- and element-positions via type classes `EmbedAsAttribute` and `EmbedAsChild`. In Hamlet Haskell expressions can be substituted using the notation #{...}. The rendering of substituted expressions is type based and specified via the type class `ToMarkup` from the blaze-html package. URLs are substituted using the notation @{...}, other templates using ^{...} and messages for i18n using _{...}. What are the reasons for providing four different syntaxes for variable subsitution if the substitution is type based? For example, different escaping mechanisms (URL escaping for routes, HTML escaping for strings) could already be accomplished with a single syntactical construct based on different type-class instances to generate markup. Best, Sebastian

On Sun, Sep 23, 2012 at 11:14 AM, Sebastian Fischer
Hello,
when writing my comparison of Hamlet with Heist and HXT (https://gist.github.com/3757918) I noticed that Hamlet and HXT have similar approaches to variable substitution but Hamlet's uses different syntax for substituting URLs, templates, and other expressions. I'd like to understand why.
In HXT Haskell expressions can be substituted using the notation <% ... %>. The rendering of substituted expression is type based and specified separately for attribute- and element-positions via type classes `EmbedAsAttribute` and `EmbedAsChild`.
In Hamlet Haskell expressions can be substituted using the notation #{...}. The rendering of substituted expressions is type based and specified via the type class `ToMarkup` from the blaze-html package. URLs are substituted using the notation @{...}, other templates using ^{...} and messages for i18n using _{...}.
What are the reasons for providing four different syntaxes for variable subsitution if the substitution is type based? For example, different escaping mechanisms (URL escaping for routes, HTML escaping for strings) could already be accomplished with a single syntactical construct based on different type-class instances to generate markup.
Best, Sebastian
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.
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 ``.
The same kinds of arguments apply to i18n messages as well. Though it
would be technically possible to instead just use a single typeclass
and force all templates to run in a certain monad, I think the
approach we have no is superior.
So tl;dr: Passing around an explicit functions and making different
types of interpolation look different is IMO better.
Michael

I rephrase to check if I understand correctly.
You want to make it an error to:
1. accidentally splice something that is no URL into URL positions and
2. splice sub-site URLs into the wrong sub site (or into the main
sub site) without proper wrapping.
Make sense, thanks!
Sebastian
On Sun, Sep 23, 2012 at 2:02 PM, Michael Snoyman
On Sun, Sep 23, 2012 at 11:14 AM, Sebastian Fischer
wrote: Hello,
when writing my comparison of Hamlet with Heist and HXT (https://gist.github.com/3757918) I noticed that Hamlet and HXT have similar approaches to variable substitution but Hamlet's uses different syntax for substituting URLs, templates, and other expressions. I'd like to understand why.
In HXT Haskell expressions can be substituted using the notation <% ... %>. The rendering of substituted expression is type based and specified separately for attribute- and element-positions via type classes `EmbedAsAttribute` and `EmbedAsChild`.
In Hamlet Haskell expressions can be substituted using the notation #{...}. The rendering of substituted expressions is type based and specified via the type class `ToMarkup` from the blaze-html package. URLs are substituted using the notation @{...}, other templates using ^{...} and messages for i18n using _{...}.
What are the reasons for providing four different syntaxes for variable subsitution if the substitution is type based? For example, different escaping mechanisms (URL escaping for routes, HTML escaping for strings) could already be accomplished with a single syntactical construct based on different type-class instances to generate markup.
Best, Sebastian
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.
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 `
`.
The same kinds of arguments apply to i18n messages as well. Though it would be technically possible to instead just use a single typeclass and force all templates to run in a certain monad, I think the approach we have no is superior.
So tl;dr: Passing around an explicit functions and making different types of interpolation look different is IMO better.
Michael

Yes, that about sums it up. My explanation focused on URLs because,
IMO, they're the most important aspect, but a similar argument can be
made for embedded templates and i18n messages.
On Sun, Sep 23, 2012 at 2:37 PM, Sebastian Fischer
I rephrase to check if I understand correctly.
You want to make it an error to:
1. accidentally splice something that is no URL into URL positions and 2. splice sub-site URLs into the wrong sub site (or into the main sub site) without proper wrapping.
Make sense, thanks!
Sebastian
On Sun, Sep 23, 2012 at 2:02 PM, Michael Snoyman
wrote: On Sun, Sep 23, 2012 at 11:14 AM, Sebastian Fischer
wrote: Hello,
when writing my comparison of Hamlet with Heist and HXT (https://gist.github.com/3757918) I noticed that Hamlet and HXT have similar approaches to variable substitution but Hamlet's uses different syntax for substituting URLs, templates, and other expressions. I'd like to understand why.
In HXT Haskell expressions can be substituted using the notation <% ... %>. The rendering of substituted expression is type based and specified separately for attribute- and element-positions via type classes `EmbedAsAttribute` and `EmbedAsChild`.
In Hamlet Haskell expressions can be substituted using the notation #{...}. The rendering of substituted expressions is type based and specified via the type class `ToMarkup` from the blaze-html package. URLs are substituted using the notation @{...}, other templates using ^{...} and messages for i18n using _{...}.
What are the reasons for providing four different syntaxes for variable subsitution if the substitution is type based? For example, different escaping mechanisms (URL escaping for routes, HTML escaping for strings) could already be accomplished with a single syntactical construct based on different type-class instances to generate markup.
Best, Sebastian
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.
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 `
`.
The same kinds of arguments apply to i18n messages as well. Though it would be technically possible to instead just use a single typeclass and force all templates to run in a certain monad, I think the approach we have no is superior.
So tl;dr: Passing around an explicit functions and making different types of interpolation look different is IMO better.
Michael

On Sun, Sep 23, 2012 at 7:02 AM, Michael Snoyman
On Sun, Sep 23, 2012 at 11:14 AM, Sebastian Fischer
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}> 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: home</a> And the following would be rejected: <a href=Home>home</a> not a url</a> <a><% Home %></a> <a><% (At Home) %></a> Though, the following would still be accepted: 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 `
`.
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:
in a template that requires
the App route? you instead need:
?
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 = my image</a>
It would fail. You would need:
foo :: MyApp App XML
foo = my image</a>
If you already had a template like:
foo :: MyApp Static XML
foo = 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

On Sun, Sep 23, 2012 at 10:15 PM, Jeremy Shaw
On Sun, Sep 23, 2012 at 7:02 AM, Michael Snoyman
wrote: On Sun, Sep 23, 2012 at 11:14 AM, Sebastian Fischer
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:
And the following would be rejected:
<a href=Home>home</a> not a url</a> <a><% Home %></a> <a><% (At Home) %></a>
Though, the following would still be accepted:
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 `
`.
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:
in a template that requires the App route? you instead need:
?
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 = my image</a>
It would fail. You would need:
foo :: MyApp App XML foo = my image</a>
If you already had a template like:
foo :: MyApp Static XML foo = 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

On Mon, Sep 24, 2012 at 12:20 AM, Michael Snoyman
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 = 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. - jeremy

On Mon, Sep 24, 2012 at 4:43 PM, Jeremy Shaw
On Mon, Sep 24, 2012 at 12:20 AM, Michael Snoyman
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 = 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
participants (3)
-
Jeremy Shaw
-
Michael Snoyman
-
Sebastian Fischer