Image generated by Midjourney

Part II of II

Designing a Functional Library

Uberto Barbini
9 min readJul 18, 2023

--

Let’s continue exploration of how to design a functional library from where we left off in the previous post.

Contents
* Handling Lists
* Assign Types
* Refactor for New Types
* Implementing ListTag
* Functional Takeaways
* Optional Tag Exercise
* Should We Write This Library?
https://pragprog.com/newsletter/
https://pragprog.com/newsletter/

Handling Lists

Our next challenge is handling lists. Specifically, our template engine should have the capability to generate multiple lines, each repeating a substitution from a list to a sub-template. We’ll start by writing a test to clarify our expectations. It might look something like this:

val template = """
Dear {title} {surname},
we would like to bring to your attention these task due soon:
{tasks} {id} - {taskname} which is due by {due}{/tasks}
Thank you very much {name}.
""".trimIndent()

val tasks = TODO("some kind of special tag")
val tags = TODO("rest of the tags including tasks")
val text = renderTemplate(template, tags)
val expected = """
Dear Mr Barbini,
we would like to bring to your attention these task due soon:
1 - buy the paint which is due by today
2 - paint the wall which is due by tomorrow
Thank you very much Uberto.
""".trimIndent()
expectThat(text).isEqualTo(expected)

Note that the {tasks} and {/tasks} tags are designed to work together. The content between these tags should repeat for each element in the tasks list.

To get this template to work, we’ll need to tweak how we use regex. Instead of extracting all matches of opening and closing tags, we’ll match each specific list tag. Once we identify the list tag block, we’ll replace the tags inside it with list elements, and output a line for each element of the list.

Implementing this solution will require us to apply each tag (with its own regex) in succession, instead of doing a single pass with the regex for all tags. While this approach may slow things down a bit, it’ll greatly enhance flexibility. We can always consider performance optimization later.

Assign Types

As we need different types of tags, we must assign them different types. This requirement means we need to modify our Tags type alias as well.

Let’s keep the Renderer as is. We can create a Tag type, which is a sealed hierarchy, that allows each instance to transform the template in some way. Here's how it looks:

typealias Renderer = (Tags) -> String

typealias Tags = (TagName) -> Tag?
sealed class Tag : (Template) -> Template {
abstract val name: TagName
}
//TODO: define the tags

Just a quick side note: the Tag class is an invokable class. Even if it may not be immediately apparent, each tag acts like a curried function. In other words it's a function that's been “loaded” with some data — the tag name and the value to replace it with — which it uses to transform a Template into another Template.

Since we’re altering the mechanism for tag substitution, we need to define different types for the various kinds of tags. At present, we have two of them: StringTag (which we've used so far) and ListTag (for list elements).

✏️ Takeaway 6: We should try to model our types in a way that closely mirrors the problem domain we want to solve. In this way, our code can express the problem and solution more intuitively.

The StringTag is simple to implement, just a replace on the text:

data class StringTag(override val name: TagName, val text: String?) : Tag() {
override fun invoke(template: Template): Template =
template.text.replace(name.value, text ?: "")
.asTemplate()
}

Refactor for New Types

But before proceeding with the ListTag implementation the list, let’s refactor the rest of the code to work with new types.

Inside our RenderTemplate class, we first need to compile a list of tags that need replacing, which we find using the same regex as before. Then, using the fold in our invoke function, we can replace each of these tags in the template, one by one. If we encounter a tag that we can’t find a replacement for, we should replace it with an empty string (as done by the removeMissingTag function):

data class RenderTemplate(val template: Template) : Renderer {

val tagRegex = """\{(.*?)}""".toRegex()

val tagsToReplace = tagRegex.findAll(template.text)
.map { TagName(it.value) }.toSet()

private fun replaceTag(currTempl: Template,
tagName: TagName, tag: Tag?): Template =
when (tag) {
null -> removeMissingTag(currTempl, tagName)
else -> tag(currTempl)
}

private fun removeMissingTag(currTempl: Template, tagName: TagName) =
currTempl.text.replace(tagName.value, "").asTemplate()

override fun invoke(tags: Tags): String =
tagsToReplace.fold(template) { currTempl, tagName ->
replaceTag(currTempl, tagName, tags(tagName))
}.text
}

And this is how the the happy case test looks like now:

@Test
fun `replace simple strings`() {
val titleTag = StringTag("title".asTagName(), "Mr")
val surnameTag = StringTag("surname".asTagName(), "Barbini")
val renderTemplate = RenderTemplate(
"""{title} {surname}""".asTemplate()
)
val tags: Tags = { x ->
when (x) {
titleTag.name -> titleTag
surnameTag.name -> surnameTag
else -> null
}
}

val text = renderTemplate(tags)

val expected = "Mr Barbini"
expectThat(text).isEqualTo(expected)
}

Implementing ListTag

All tests are passing now, so we can move forward with the implementation of ListTag. As it's required to generate a repeating element, and since the element can consist of multiple tags, ListTag needs to take a list of Tags as a constructor parameter, in addition to the tag name:

data class ListTag(override val name: TagName, val subTags: List<Tags>) : Tag(){
override fun invoke(template: Template): Template = TODO()
}

With the type signature sorted out, we can proceed to write the test that will guide the implementation of ListTag.

We’ll write a test that creates an order for some household items. In this test, we need to define a template with nested tags and provide the values to be replaced in the ListTag:

@Test
fun `replace elements from a list`() {
val renderer = RenderTemplate(
"""{title} {surname} order:
|{items} {qty} of {itemname} {/items}
|Total: {total} pieces
""".trimMargin().asTemplate()
)

val itemsTags = listOf(
" 4" to "glasses",
"12" to "plates"
).map { (qty, name) ->
tags(
"qty" tag qty,
"itemname" tag name
)
}
val tags = tags(
"title" tag "Mr",
"surname" tag "Barbini",
"total" tag "16",
"items" tag itemsTags
)

val text = renderer(tags)

val expected = """Mr Barbini order:
| 4 of glasses
| 12 of plates
|Total: 16 pieces""".trimMargin()
expectThat(text).isEqualTo(expected)
}

To keep this test easy to read, I’ve used some features of Kotlin’s Domain Specific Language (DSL), including infix functions and varargs arguments. So I’ve also defined three small helper functions:

fun tags(vararg tags: Tag): Tags =
tags.associateBy { it.name to it }::get

infix fun String.tag(value: String) = StringTag(this.asTagName(), value)
infix fun String.tag(value: List<Tags>) = ListTag(this.asTagName(), value)

✏️ Takeaway 7: Using Kotlin’s DSL features can improve the readability of your code. But don’t overuse them, as they could transform your code from concise to cryptic.

As you may have noted, I used some maps to hold the tags in our tests. This move doesn’t contradict what we discussed earlier, as there’s nothing wrong in using maps to store our data. What we want to avoid is our library having an unnecessary dependency on them.

I think the final result is clean and easy to read. Now, we just need to implement ListTag to make the test pass.

Essentially, we need to select all the text between the opening and closing tags, remove the tags themselves, and then apply the template substitution for each sub-tag:

data class ListTag(override val name: TagName, 
val subTags: List<Tags>): Tag(){

private val strippedTag = name.value.drop(1).dropLast(1)

private val tagRegex = """\{$strippedTag}(.*?)\{/$strippedTag}"""
.toRegex(RegexOption.DOT_MATCHES_ALL)

private fun generateMulti(subTemplate: Template): String =
subTags.joinToString(separator = "\n") {
RenderTemplate(subTemplate)(it)
}
private fun MatchResult.asSubtemplate(): Template =
value.drop(name.value.length)
.dropLast(name.value.length + 1).asTemplate()

override fun invoke(template: Template): Template =
template.text.replace(tagRegex) {
generateMulti(it.asSubtemplate())
}.asTemplate()
}

I arrived at this code by breaking down the problem into smaller, simpler sub-problems that could be solved by individual, concise functions. Then, I started rearranging the code, refining function names, and breaking the functions down even further.

✏️ Takeaway 8: To tackle complex problems, break them down into smaller, simpler sub-problems. Solve them with micro functions, and then compose them together. Don’t forget to use recursion when you can.

Now, it’s easy to adjust our initial acceptance test and check that it passes.

You can find the complete code on GitHub at https://github.com/uberto/templatefun.

Functional Takeaways

I hope you’ve enjoyed this journey, I’ve tried to illustrate the reason I really appreciate the functional approach, even for small tasks.

Here’s a recap of all the takeaways from both Parts I and II of this lesson in defining a functional library:

✏️ Takeaway 1: It’s easier to compose pure functions with only one parameter.

✏️ Takeaway 2: It’s better to name all our types rather than using primitive types.

✏️ Takeaway 3: Let’s go minimal. Our library should depend on as few other types as possible.

✏️ Takeaway 4: Invokable classes offer a Kotlin idiomatic solution for curried functions.

✏️ Takeaway 5: Avoid handling expected errors with exceptions.

✏️ Takeaway 6: We should try to model our types in a way that closely mirrors the problem domain we want to solve. In this way, our code can express the problem and solution more intuitively.

✏️ Takeaway 7: Using Kotlin’s DSL features can improve the readability of your code. But don’t overuse them, as they could transform your code from concise to cryptic.

✏️ Takeaway 8: To tackle complex problems, break them down into smaller, simpler sub-problems. Solve them with micro functions, and then compose them together. Don’t forget to use recursion when you can.

As a final farewell, I will leave an exercise to hone your functional design skills.

Optional Tag Exercise

Define a new type of Tag that can either show or hide a section of text based on the value of a boolean variable. For example, this template will display the final Christmas wishes only if the isXmas tag has a positive value:

val template = """{title} {surname}, 
|thanks for your order.
|{isXmas}Merry Christmas!{/isXmas}""".trimIndent()

Give it a try! You can find my solution in the same repository.

Should We Write This Library?

To conclude, I’d like to address the question of whether or not it makes sense to write such a library when there are already many templating solutions in the Java open source world. I might argue that this exercise is purely for didactic purposes, and while that’s partly true, it isn’t the whole story. More than once, I wrote and put it into production a small opinionated library to solve my problem, instead of relying a widely used framework or library.

So, why did I choose this path?

For one, there was no library that perfectly matched the design of the application that would use it, so adopting a templating library would require making modifications I wasn’t keen on — for example having too many dependencies or requiring using annotation or code generation.

Additionally, the open source libraries are often fairly large, whereas my actual requirements were quite limited. If I can meet my needs with 100 lines of code, it makes more sense to do that than to import a 100,000 lines of code library loaded with unnecessary features.

Another factor is confidence. I trust code that I’ve written and thoroughly tested more than I trust the code in a large open source library, especially when it comes to security.

And finally, there’s the time factor. I probably spent around 3–4 hours writing the code of this post, which is likely less than the time it would’ve taken me to select, install, and learn how to use an existing templating framework.

Of course, as a general rule, I agree that it’s best to avoid reinventing the wheel. If there were a similar, small library with the same functional approach to templating, I certainly would’ve used it. But using an overly complex framework for a simple task feels like driving an 18-wheeler to pick up groceries at the supermarket. It’s all about choosing the right tool for the job.

💬 If you are interested posts like this one, please follow me on Medium or on my twitter account, ramtop.

If you are interested in the topic discussed in this post, you should consider buying my book, From Objects to Functions, where I discuss them much more in depth.

book cover featuring a host of white paper cranes with one blue crane out in front

--

--

Uberto Barbini
The Pragmatic Programmers

JVM and Kotlin independent consultant. Passionate about Code Quality and Functional Programming. Author, public speaker and OpenSource contributor.