Wasabi: How Do I Picture Functions
November 6th, 2007 by Stefan Rusek
Coming up with a clean way to generate HTML was one of the first features that we needed to spec out. Any sufficiently large web application has a complicated database query whose information needs to be printed out in different, possibly disconnected parts of the page. For example, say you wanted to print summary information for a complex, database-backed table before the table itself. You could add a COUNT(*) (or some aggregate functions) to get the summary information, then reissue the query to get the data, but then you have to execute your complex query twice. So what you want to do is generate the table and gather the data you need first, then generate the summary information.
(I should add a little information about ASP. ASP is a stream-centric architecture. This is in contrast to ASP.NET, where you construct a tree of HtmlControls that represent the HTML and then serialize the tree into html. The stream-centric model uses fewer resources, but it is less flexible regarding the order that actions can be performed.)
I was considering using one of two models to solve this problem. The first was an ASP.NET-like model where we would have a bunch of objects representing HTML nodes. Once they were ready, we would convert them to actual HTML text. The other model was more like a lot of JS frameworks, and used a lot of functions along with arrays and dictionaries to generate HTML. Each of these had the advantage of solving the data generation ordering problem, but neither of these really felt satisfactory. So I got some of the developers to together to discuss the issue. Joel asked why we didn't just use picture functions. None of us knew what he was talking about, so he explained them to us. It turns out that a picture function is just a function that uses ASP's built-in content generation mechanism to generate a "picture" of the content, but does nothing else. So the following is a picture function:
/ \ / \
|\_ | _/|
( _'#`_ )
|/ | \|
This didn't immediately solve the problem, but it did give us something to think about. The biggest problem with this method is that we didn't have any way to normalize the data, so HTML validation would be close to impossible. The advantage was that we already had a bunch of code in FogBugz that fit this pattern.
Another nagging concern was that I didn't really have much respect for the ASP method of embedding HTML in code. As I thought about it some more, I began to realize that not all uses of embedding HTML were bad. The above function is probably the cleanest way to generate a PRE tag with a flower in it that I can think of. The problem with ASP's method of generating HTML is not that technique itself, but that it has been so misused in the past. Like many programming features over the past half century, it has been abused to create a horrible mess, but, done properly, it makes extremely clean code possible. (In fact the latest version of VB.NET has ASP-like mechanisms built-in for XML data generation, and Lisp has always been able to mix data and code together.)
Next we needed a way to use picture functions to solve the problem I already mentioned. The first feature that Wasabi got that VBS doesn't have is the ability to attach metadata to Classes and Functions. So we added a Picture metadata attribute, and a new language keyword PictureOf. So the problem is solved by the following code:
<Picture> Sub TableBody( rs, ByRef count, ByRef total)
Dim rs = GetData()
Dim count, total
Dim sTable = PictureOf TableBody(rs, count, total)
%><div>The average is <%=total/count%></div><%
Under the covers, we have a global object called OurResponse that wraps the Response object and has a stack of StringBuilder objects. To take a picture of a function the compiler generates a call to OurResponse.Push(), which creates a new StringBuilder on the stack. To retrieve the picture, the compiler calls OurResponse.Pop(), which converts the StringBuilder to a string and removes it from the stack. This allows a picture function to take the picture of another function if it needs to. So the PictureOf line above gets generated as:
TableBody rs, count, total
sTable = OurResponse.Pop()
And all calls to Response.Write are replaced with OurResponse.Write. In ASP we only do the replacement for lambdas, picture functions, and functions called by picture functions. (You must do this for lambdas because you never know if they will be called in a picture context.)
The ability to take pictures of functions has turned out to be a very valuable feature that makes a lot of code easier and cleaner to write. The really cool thing about picture functions is that, after they were spec'ed out, they started turning up in other specs and have become instrumental in a number of Wasabi features: client-side code generation, meta programming, memoization, caching, and of course contributing to making Wasabi just that much more awesome.