We can render this to a HTML string and print the result just by saying:

printfn "%s" (Renderer.RenderToString page)

So far so good.

Now, I'm also a big fan of Julius Sharpe (a Family Guy writer), so I want to include his tweets too. To avoid repeating ourselves, we'll create a 'twitter' function, parameterized by user name, and then we'll just call this function in the layout:

Note how I also now pass a seed to generate div IDs, to keep the code pure. But we have another, bigger problem: when rendering this page we end up with two references to jQuery and two references to jquery.tweet.js! We could move those references out of the 'twitter' function and put them in the layout, but that would pretty much defeat the purpose of this abstraction, it wouldn't be self-contained anymore, it wouldn't be reusable, it wouldn't be composable.

We've all been here. Some people just give up (or don't think much of it) and put the scripts outside the component. Others write a helper or an asset manager (basically, calling a function that keeps track of assets). This leads to a loss of purity, and with it, composability, unless you're willing to thread along this helper's state, which is not a nice prospect unless your whole code is monadic.

Another approach to the problem is to just remove the duplicate <script> tags. This is what Lift (a Scala web framework) does: you can insert <head> elements anywhere you want, and before rendering, Lift will move the contents of all inner <head>s to the one and only <head> that should be in a HTML document, deduplicating elements in the process.

Implementing something similar with Wing Beats is quite easy. Instead of scanning for <head> elements as Lift, we'll just scan for <script> elements. Also, we won't move these elements, we'll just remove the duplicates, i.e. leave the first occurrence of each script. Here's some code that does this:

Now, this deduplication function isn't particularly efficient: no tail calls, List.rev... it will blow the stack if given a sufficiently deeply nested structure (a few tests indicate that it dies at around 2600 nested elements, not a bad number nevertheless). More generically, we'd want to define a generic catamorphism over the Wing Beats tree (check out Brian McNamara's excellent series on catamorphisms) and then write deduplication (or any other kind of tree processing) using that fold.

The point is, as you can see it was pretty easy to manipulate and abstract HTML fragments to simple, reusable, pure functions... because Wing Beats makes HTML elements truly first-class citizens. It models HTML as a tree, and not just as a string.

Erik Meijer already showed 11 years ago that view engines that don't make HTML fragments first-class citizens don't compose. Back to 2011, lots of view engines still suffer from this (Razor included).

Bottom line: if you're templating unstructured text, then by all means use a generic text template engine. But if you're writing a web application and dealing with HTML, treating HTML as first-class values instead of unstructured text buys you composability: you can use the full power of the host language and you can handle HTML directly as a tree.

In a future post about this subject, I'll try to categorize the different approaches to HTML generation and analyze them from the point of view of composability.

Wednesday, July 6, 2011

A couple of posts ago I introduced FsConneg, a stand-alone HTTP content negotiation library written in F#. One of my goals in making it stand-alone is that it could be reused across projects, maybe eventually getting integrated into Frank or MonoRail3. Here I will show some potential integrations with Figment.

Let's start with a trivial function:

let connegAction _ = "hello world"

We want to bind this to an URL (i.e. GETting the URL would return "hello world" obviously), and negotiate the response media type with the user agent. The server will support XML and JSON.

We can build a dispatch table with the supported media types and corresponding actions:

Briefly, this function takes a table of acceptable media types and associated writers (just like the table we created above) and a "partial" action, and returns an action where the media type is negotiated with the user agent.

Armed with this function, let's bind the negotiated action to an URL:

get "conneg1" (negotiateActionMediaType writers connegAction)

For a second URL we'd also like to offer text/html. Here's a simple parameterized Wing Beats page:

I think this snippet is pretty intuitive, even if you're not familiar with Figment or functional programming. I'll explain anyway:

ifMethodIsGet and ifPathIs are routing functions built into Figment. The &&. operator composes these routing functions as expected, i.e. the resulting routing function must satisfy both conditions. This is explained in more detail in my introduction to Figment.

The >>= operator is a monadic bind. The function

connegAction >>= Result.xml

is equivalent to:

result {
let! result = connegAction
return! Result.xml result }

or:

fun ctx ->
let result = connegAction ctx
Result.xml result ctx

Except the first one is evidently more concise. I explained this in more detail in my last post.

ifAcceptsAny uses FsConneg to determine if any of the media types in the list is acceptable to the client. Its definition is quite simple:

The important thing to notice about this last example is that using routing like this doesn't yield proper content negotiation. If a user-agent requests with "Accept: application/json, application/xml;q=0.8" (i.e. prefers application/json), the code above will respond with application/xml, disregarding the client's preferences, simply because the action for application/xml was defined before application/json.

A common way to select a response media type is using extensions in the URL. Twitter used to do this, until they scrapped XML support altogether. MySpace still does it. Similarly, others use a query string parameter to select the media type, like last.fm.

Some prefera compromisebetween conneg and extensions, by implementing an extensionless URL that supports content negotiation and then the same URL with extensions as a means to override conneg and work around possible client issues, or just selecting a media type without messing with headers.

As I said before, these are all potential integrations. I specifically want these libraries to be as non-opinionated as possible. But at the same time, I want to provide all the tools to let the developer easily create her own opinions/conventions to fit her project, using library calls and standard language constructs instead of having to learn framework extension points. For example, notice that the dispatch tables I explained are all regular lists of strings and functions, which are then mapped and iterated just like any other list. More on this in a future post.