Photo by Chris Briggs on Unsplash

PART III OF III

From Objects to Functions

How to Convert Our Functional Design into Code

The Pragmatic Programmers
6 min readJun 27, 2022

--

https://pragprog.com/newsletter/

Continuing from part two, we are implementing a simple URL shortener in Kotlin, following functional programming principles.

We completed the walking skeleton of our application. It’s time now to work on the functions that provide the actual functionality:

fun generate(req: Request): Response = ...fun expand(req: Request): Response = ...

Using the Generate Function

Let’s start with the generate function: it must return a ShortUrl from the Url that the user wants to shorten.

I usually use pen and paper to draw something like this:

The function to generate a shortened URL as a chain of type transformations

Of course it’s a process of trial and error, drawing and erasing, until I’m confident I understand all the types and transformations involved.

What I like most about these kind of diagrams is that they are very simple to draw and understand. We are going to use a simple chain of transformations, each one clear enough to be translated directly into code.

In my experience, defining the behavior of the application drawing simple arrows is a much more productive affair than trying to model applications using design patterns like MVC, MVP, MVVM and their variations. Especially if done with the whole team, it keeps people concentrated on the data flows and transformations instead of encouraging bickering about opinions.

Now we can list the functions needed from the diagram of our generate function:

1) Request -> FullUrl
2) FullUrl + DbConnection -> ShortId
3) ShortId -> ShortUrl
4) ShortUrl -> HtmlPage
5) HtmlPage -> Response

Function number two is quite interesting because it involves external effects. We will look at how to implement it soon, but first we need define our types.

Defining Our Types

If we focus on transformation, it’s very important to avoid, as much as possible, using primitive types like String, and Int. Instead, we can wrap them inside our domain classes and use these in our functions. Looking at our diagram, we can count four new types we need.

So let’s start defining them using Kotlin’s convenient data class syntax:

data class ShortUrl(val raw: String) //represent the shortened urldata class FullUrl(val raw: String) //represent the original full urldata class ShortId(val number: Int) { //the primary key of our store 
val key: String = number.toString()
}
data class HtmlPage(val raw: String)

We’ll proceed now with the implementation, starting with the impure functions.

Ensuring Functional Persistence

It would be nice if functional programs could avoid using impure functions at all, but such programs would be pretty useless. So we have to accommodate referentially opaque functions in our design.

Since we need to remember the association between the ShortId and the FullUrl in case of a restart of our application, we have to use external storage. We have many possible candidates to choose from, but let’s keep it simple and use Redis, a data store service that offers a simple HTTP API. But the general principle can be applied to any database.

In functional programming, there are many techniques that allow you to mix pure functions with IO. The important principle they all have in common is to keep the external effects related code clearly separated by the code of pure functions. In other words, we don’t want our effects to be swept “under the carpet,” but instead want them to be explicitly declared and put in separate functions.

In this case, what we are going to do is to put our impure functions in a separate singleton to mark them clearly. In the book I describe more powerful and sophisticated solutions, but for such a small program, marking them in this way is enough.

The RedisPersistence singleton object:

object RedisPersistence {    private val redis = Jedis() //hidden context    fun retrieveUrl(index: ShortId): FullUrl? =
redis.get(index.key)
?.let(::FullUrl)
fun saveUrl(url: FullUrl): ShortId =
redis.incr("counter").toInt()
.let(::ShortId)
.also { redis.set(it.key, url.raw) }
}

Note that our impure functions still work as if they were pure functions. They have an hidden context — in this case, the database connection, but it is private inside the singleton. In this way, retrieveUrl and saveUrl don’t share hidden state with the rest of the code, which can stay pure.

Producing HTML

Here are the HTML related functions — little more than a template. First, here’s the page to show the shortened URL:

fun htmlShortenedPage(short: ShortUrl): HtmlPage = HtmlPage(
“””<html><body><h1>
The shorten Url is ${“/x/${short.raw}”.toAnchor()}
</h1></body></html>
“””.trimIndent()
)

And then the page that will return the original URL as a link:

fun htmlFullUrlPage(fullUrl: FullUrl): HtmlPage = HtmlPage( 
“””<html><body><h1>
Full Url is ${fullUrl.raw.toAnchor()}
</h1></body></html>
“””.trimIndent()
)
private fun String?.toAnchor(): String? =
if (this != null) “””<a href=”$this”>$this</a>””” else “none!”

Creating the Chain of Transformations

We defined the working of the function as a chain of type transformations. In code:

fun generate(req: Request): Response =
req.queries(“url”).firstOrNull()
?.let(::FullUrl)
?.let(RedisPersistence::saveUrl)
?.let(::toShortUrl)
?.let(::htmlShortenedPage)
?.let(::toResponse)
?: Response(NOT_FOUND).body(“Url not specified”)

To chain the functions together I used the let function with a nullable operator, so that we can treat any null like an error, skipping the rest of the chain.

Expand Function

To expand a mnemonic two words to the original URL, we need to do the same operations in reverse. We can draw it like this:

The function to expand a shortened URL as a chain of type transformations

In total, we need to create at least five functions (the same number as in the diagram):

1) Request -> ShortUrl
2) ShortUrl -> ShortId
3) ShortId + DbConnection -> FullUrl
4) FullUrl -> HtmlPage
5) HtmlPage -> Response

The expand function is where we define the API to expand a shortened URL as a function from Request to Response. If we compare this code with the arrows diagram we can see they are representing exactly the same thing: we compose simple functions to obtain the application behavior.

We also need to consider the case in which the ShortUrl doesn’t exist in the database and we need to show a nice error to the user. Since we are not using exceptions (which would make the code referentially opaque), we will consider the null as a possible response from the database, indicating a 404 error:

fun expand(req: Request): Response =
req.path(“short”)
?.let(::ShortUrl)
?.let(::toShortId)
?.let(RedisPersistence::retrieveUrl)
?.let(::htmlFullUrlPage)
?.let(::toResponse)
?: Response(NOT_FOUND).body(“Url not found!”)

Final Result

Now that the two functions are finished, we can start the application and see how it works in both ways. We can pass a URL to create a short, easy-to-remember, two-word key:

And then we can retrieve the original URL, passing our two-word shortened key:

If you want to try it yourself, I put the code for this exercise on GitHub:

Conclusion

I hope that this little example gave you an idea of what I mean by thinking in morphisms.

In the end, we implemented the whole service using five simple functions and a few types. Even if it’s a small example, I think it shows why I love using functional programming: it’s easy to design and it results in little code that is easy to read (and test).

If you are interested in learning more, I explain all this in much more detail in my book, including a better technique for handling impure functions, how to avoid exceptions, logging, and the event sourcing pattern.

Parts one and two of this series:

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

If you really really liked it, consider buying my book, From Objects to Functions:

You can save 35% off the ebook using promo code uboop_medium_35 now through July 31. Promo codes are not valid on prior purchases.

--

--

The Pragmatic Programmers

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