Since Haskell is pure and has a strong static type system, the first thing to think about when discussing this question is what types will be involved. Since templates are processed at runtime, they do not have access to Haskell's type system. This puts us square in the middle of byte-land. No generalized looping over things like the list of registered users or recent posts. No type-safe evaluation of arbitrary expressions. It is my contention that you're better off solving these problems with a language designed for that purpose...i.e. in Haskell splices, and not in HTML templates.
Let's continue the example of displaying blog posts used previously. We developed a function
postSplice :: Splice m(don't worry about the 'm' for now) that our templates could use to render a single post using whatever markup the template designer specified. Now we want to use a looping structure to reuse that code for a list of posts. Where does this list come from? It comes from Haskell code (not templates), so it's logical to do our looping abstraction in Haskell. I would probably refactor the postSplice function as follows.
renderPost :: Monad m => Post -> Splice m renderPost post = do runChildrenWithText [("postTitle", postTitle post) ,("postBody", postBody post)]
Notice that this function is no longer a splice. It's a function that takes a Post as input and generates a splice as output. Now we can use the new mapSplices function introduced in Heist 0.5.1.0 to render a list of posts.
renderPosts :: Monad m => [Post] -> Splice m renderPosts = mapSplices renderPost
That was easy. Now one more step gets us a splice that we can bind to the <recentPosts/> tag and use in our templates.
recentPostsSplice :: Splice Application recentPostsSplice = getRecentPosts >>= renderPosts
This splice can be used in a template exactly the same way we did before. The only difference is that the view we passed into the splice is now applied to each post in the list and the results are concatenated.
I specialized this function to the Application monad which will contain functionality specific to your application that provides the necessary context for the getRecentPosts function. I left out the details of that function because those details aren't something the template author should worry about. And here we get to the main point. The core of Heist's keep-application-logic-out-of-the-view philosophy boils down to the idea that you're better off creating a domain-specific set of query functionality than exposing too much generality to the templates.
One likely avenue of domain-specific query generalization might be to look at only the posts with a certain tag. We might do something like this.
postsWithTag :: Text -> Splice Application postsWithTag tag = getTaggedPosts tag >>= renderPosts postsWithTagSplice :: Splice Application postsWithTagSplice = do node <- getParamNode maybe (return ) postsWithTag $ getAttribute "tag" nodeThen with the appropriate splice binding, we can do this in our templates.
<postsWithTag tag="heist"/>Or, instead of getting the tag from an attribute of the param node, we could do this instead.
postsWithTagSplice :: Splice Application postsWithTagSplice = do tag <- lift $ getParam "tag" maybe (return ) postsWithTag tagIn the Snap template project, Application has a MonadSnap instance, so we can lift the getParam function into the splice's monad. This gets the tag from the HTTP request parameters of whatever request is being processed when <postsWithTag> is rendered. This makes it easy to tie into values gathered from a form or even specified manually in the query string.
Hopefully this demonstrates how templates can interact with looping and control flow defined in Haskell code using the gorgeous syntax we all know and love, and how we can leverage Haskell's powerful abstraction constructs to create nice domain-specific markup languages that are easy for designers to incorporate into their web designs. In the next post I'll look more closely into the abstractions available exclusively in templates.
EDIT: To clarify, the splices in this post will be used in exactly the same way the <post> splice was used in the last post. The different splice just changes the context in which the passed-in parameters are used. For example:
<recentPosts> <h1><postTitle/></h1> <div><postBody/></div> </recentPosts>