Scala Templates

I've recently been messing around with Scala and figured I'd tackle a problem I've already solved in other languages. Here is the template kata implemented using a regex to match the placeholders:

class Template(val tmpl: String) {
    def render(context: Map[String, Object]): String = {
        return """\$(\w+)""".r.replaceAllIn(tmpl, m => {
            context.getOrElse(m.group(1), "").toString()
        })
    }
}

Here's a couple things I like:

  • Scala's Maps have a getOrElse method, yay! It's a little more verbose than Python's get method, but at least I don't have to write it my self.
  • It's actually shorter than my first implementation in JavaScript, including the type annotations.
  • I actually really like the class parameter, implicit field, thing (val tmpl: String). It handles the "The constructor takes and argument and binds it to a field." use-case very nicely.
  • Scala's lambda expressions are very concise, and it's hard not to like that.

Here are the tests written in a BDD style using ScalaTest:

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers

class TemplateSpec extends FlatSpec with ShouldMatchers {
    val EMPTY = Map[String, Object]()

    private def test(tmpl: String, expected: String, ctx: Map[String, Object] = EMPTY) {
        new Template(tmpl).render(ctx) should equal (expected)
    }

    "A template with no tokens" should "return a blank string if the given string is blank" in {
        test("", "")
    }

    it should "return a non-blank string unchanged" in {
        val identity = "hello, world"
        test(identity, identity)
    }

    "A template with one token" should "replace missing values with the empty string" in {
        test("$name", "")
    }

    it should "replace the token with value from the context" in {
        test("$name", "Aaron", Map("name" -> "Aaron"))
    }

    it should "leave the rest of the string unchanged" in {
        test("Hello $name!", "Hello Aaron!", Map("name" -> "Aaron"))
    }

    "A template with two tokens" should "replace each token its value from the context" in {
        test("Hello $name! It is a $attitude day.", "Hello Aaron! It is a wonderful day.", Map(
            "name" -> "Aaron", "attitude" -> "wonderful"
        ))
    }

    it should "replace each missing value with the empty string" in {
        test("Hello $name! It is a $attitude day.", "Hello Aaron! It is a  day.", Map(
            "name" -> "Aaron"
        ))
    }

    it should "replace adjacent tokens with their respective values" in {
        test("$me$you", "AaronJohn", Map("me" ->"Aaron", "you" -> "John"))
    }

    it should "replace every occurrance of each placeholder" in {
        test("$a-$b-$a-$b", "AAA--AAA-", Map("a" -> "AAA"))
    }
}

They're a little verbose, but as I mentioned in a previous post, you can extract documentation from tests like these:

TemplateSpec:
A template with no tokens
- should return a blank string if the given string is blank
- should return a non-blank string unchanged

A template with one token
- should replace missing values with the empty string
- should replace the token with value from the context
- should leave the rest of the string unchanged

A template with two tokens
- should replace each token its value from the context
- should replace each missing value with the empty string
- should replace adjacent tokens with their respective values
- should replace every occurrance of each placeholder

JavaScript Templates (redux)

In the spirit of code kata (the origional problem was a kata) here is an alternative solution to the JavaScript templating problem:

var template = (function(){
    var grammar = parsec.grammar([
        parsec.token("placeholder", "[$]\\w+"),
        parsec.token("literal", "[^$]+")
    ]);

    with(grammar){
        root("template", placeholder.or(literal).repeated().then(eof));
    }

    return {
        render: function render(source, context){
            var handlers = {
                literal: function(node){
                    return node.v;
                },

                placeholder: function(node){
                    var key = node.v.substring(1);
                    return context[key] || "";
                },

                template: function(node){
                    return $.map(node.children, evaluate).join("");
                }
            };

            function evaluate(node){
                return handlers[node.type](node);
            }

            var root = grammar.parse(source);
            return evaluate(root);
        }
    };
})(parsec.util);

It's a bunch longer. It's not quite ten time's longer, but it's kind of close... and it passes exactly the same test suite and has the same features, so what, if anything is good about it?

For one thing, it uses a completely different approach. It builds a recursive descent parser using a parsing library that I put together that's based on Parsec. The basic idea of Parsec, and other parser combinator libraries, is that you define simple parsers and then compose them into more complicated parsers using combinators. What this means is that, while it is not necessary here, this approach can handle much more complicated languages than the regex based approach.

Oh, and there was one little nugget of joy in there that deserves special mention. You, might have missed it, so I'll show it again:

with(grammar){
    root("template", placeholder.or(literal).repeated().then(eof));
}

That's right, a with statement. I bet you never though you would see somebody actually try to use one of those. Sure, with statements are widely reviled, they are not one of the good parts, and they're dead in EC5 strict mode... but embedded DSLs are one of the places where you're allowed to cheat like there's not tomorrow. 'sides, $.extend(window, grammar); is your other option for scope manipulation and that's just plain evil.

JavaScript Templates

While playing around with autotest-style QUnit, I needed something to work on that actually needed some tests. Naturally, I settled on the same problem that they did in the video: Given a string with $-placeholders, replace the placeholders with values from a context object. Here's the code I came up with:

var template = (function(){
    return {
        render: function(source, context){
            return source.replace(/\$\w+/g, function(match){
                var key = match.substring(1);
                return context[key] || "";
            }) ;
        }
    };
})();

Dead simple, right? Four actual lines of code and a bunch of setup to put it in a fake module. Here are the tests:

$(function(){
    function replace(source, expected, context, msg){
        equals(template.render(source, context), expected, msg);
    }

    module("template.render");
    test("with no tokens", function() {
        replace("", "", {},
            "returns blank string if the given string is blank"
        );

        replace("hello, world", "hello, world", {},
            "returns a non-blank string unchanged"
        );
    });

    test("with one token", function(){
        replace("$name", "", {},
            "replaces token with empty string if no value in context"
        );

        replace("$name", "Aaron", {name: "Aaron"},
            "replaces token with value from the context"
        );

        replace("Hello $name!", "Hello Aaron!", {name: "Aaron"},
            "leaves the rest of the string unchanged"
        );
    });

    test("with two tokens", function(){
        replace(
            "Hello $name! It is a $attitude day.",
            "Hello Aaron! It is a wonderful day.",
            {name: "Aaron", attitude: "wonderful"},
            "replaces each token with the value from the context"
        );

        replace(
            "Hello $name! It is a $attitude day.",
            "Hello Aaron! It is a  day.",
            {name: "Aaron"},
            "replaces each token without a value in the context with the blank string"
        );

        replace("$me$you", "AaronJohn", {me: "Aaron", you: "John"},
            "replaces adjacent tokens with their respective values"
        );
    });
});

Kind of verbose, but they're actually kind of interesting. They're written in a BDD style, or as much of a BDD style as QUnit will allow. You can easily imagine a tool that extracts:

template.render
    with no tokens
        - returns blank string if the given string is blank
        - returns a non-blank string unchanged

... from tests like these. Indeed, such tools exists for RSpec and there is a BDD JavaScript testing framework, imaginatively named: JSpec, which I expect provides similar tools for it's tests.

It was an interesting experience to work my way through the tests one at a time, implementing that simplest thing that could possibly work. Though, It is kind of discouraging that the test suite is so much longer than the implementation, especially considering this code is so easily tested.