Getting started with FP in Kotlin and Arrow: Typeclasses

15 minute read

I’ve recently made the effort to try and pick up Kotlin (again). The last couple of years I’ve been doing mostly scala work (with some other languages in between), and was wondering how the functional concepts of scala programming transfer to Kotlin. Main reason is that I don’t see myself returning to Java any time soon but, Kotlin seems to fix many of the verbose parts of Java. With the Arrow Kotlin also gets some FP concepts, so it looked like the right time to really dive into Kotlin, and see how FP in Kotlin holds up.

We’ll just start this series with looking at how we can do typeclasses using Arrow in kotlin. Before we start a quick note. In these examples I’ve used Gson for JSON marshalling, a better approach would probably have been using a more functional / immutable JSON library. So I might change that if I ever get the time for it.

Setting up the environment

Since this is the first article, we’ll start with setting up the environment. I’ve just followed the instructions on Arrow, and ended up with the following gradle files:

build.gradle

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.0'
}

group 'org.smartjava'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
    jcenter()
    maven { url 'https://jitpack.io' }
    maven { url 'https://dl.bintray.com/spekframework/spek-dev' }
}

dependencies {

    testImplementation ("org.spekframework.spek2:spek-dsl-jvm:$spek_version")  {
        exclude group: 'org.jetbrains.kotlin'
    }
    testImplementation 'org.amshove.kluent:kluent:1.45'
    testRuntimeOnly ("org.spekframework.spek2:spek-runner-junit5:$spek_version") {
        exclude group: 'org.junit.platform'
        exclude group: 'org.jetbrains.kotlin'
    }

    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    compile "ch.qos.logback:logback-classic:1.2.3"
    compile "io.ktor:ktor-gson:$ktor_version"

    compile "io.ktor:ktor-server-core:$ktor_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"

    compile "io.arrow-kt:arrow-core:$arrow_version"
    compile "io.arrow-kt:arrow-syntax:$arrow_version"
    compile "io.arrow-kt:arrow-typeclasses:$arrow_version"
    compile "io.arrow-kt:arrow-data:$arrow_version"
    compile "io.arrow-kt:arrow-instances-core:$arrow_version"
    compile "io.arrow-kt:arrow-instances-data:$arrow_version"
    kapt    "io.arrow-kt:arrow-annotations-processor:$arrow_version"

    compile "io.arrow-kt:arrow-free:$arrow_version" //optional
    compile "io.arrow-kt:arrow-instances-free:$arrow_version" //optional
    compile "io.arrow-kt:arrow-mtl:$arrow_version" //optional
    compile "io.arrow-kt:arrow-effects:$arrow_version" //optional
    compile "io.arrow-kt:arrow-effects-instances:$arrow_version" //optional
    compile "io.arrow-kt:arrow-effects-rx2:$arrow_version" //optional
    compile "io.arrow-kt:arrow-effects-rx2-instances:$arrow_version" //optional
    compile "io.arrow-kt:arrow-effects-reactor:$arrow_version" //optional
    compile "io.arrow-kt:arrow-effects-reactor-instances:$arrow_version" //optional
    compile "io.arrow-kt:arrow-effects-kotlinx-coroutines:$arrow_version" //optional
    compile "io.arrow-kt:arrow-effects-kotlinx-coroutines-instances:$arrow_version" //optional
    compile "io.arrow-kt:arrow-optics:$arrow_version" //optional
    compile "io.arrow-kt:arrow-generic:$arrow_version" //optional
    compile "io.arrow-kt:arrow-recursion:$arrow_version" //optional
    compile "io.arrow-kt:arrow-instances-recursion:$arrow_version" //optional
    compile "io.arrow-kt:arrow-integration-retrofit-adapter:$arrow_version" //optional
    compile "org.jetbrains.kotlin:kotlin-script-runtime:1.3.10"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

apply plugin: 'application'
apply plugin: 'kotlin-kapt'

mainClassName = 'io.ktor.server.netty.EngineMain'

generated-kotlin-sources.gradle

apply plugin: 'idea'

idea {
    module {
        sourceDirs += files(
                'build/generated/source/kapt/main',
                'build/generated/source/kapt/debug',
                'build/generated/source/kapt/release',
                'build/generated/source/kaptKotlin/main',
                'build/generated/source/kaptKotlin/debug',
                'build/generated/source/kaptKotlin/release',
                'build/tmp/kapt/main/kotlinGenerated')
        generatedSourceDirs += files(
                'build/generated/source/kapt/main',
                'build/generated/source/kapt/debug',
                'build/generated/source/kapt/release',
                'build/generated/source/kaptKotlin/main',
                'build/generated/source/kaptKotlin/debug',
                'build/generated/source/kaptKotlin/release',
                'build/tmp/kapt/main/kotlinGenerated')
    }
}

gradle.properties

kotlin.code.style=official
ktor_version=1.0.1
arrow_version=0.8.1
spek_version=2.0.0-alpha.2

Note that if you want to use the annotation processing and you use Intellij Idea for development, you might run into the issue that no sources are being generated for the Arrow annotations. The reason is that Intellij uses it’s own system to build the sources, and not necessary your complete gradle file. This can easily be fixed though by changing enabling the following setting:

Intellij Config

At this point we should be able to use the annotations provided by Arrow and all the other provided FP functionality.

Creating typeclasses

I’m just going to assume you already know what typeclasses are. If not there are many resources out there that explain what they do and why they are really useful:

Typeclasses are also often used to marshal types from JSON to a domain object and back again. So in this example we’re going to use that as the example. We’ll start with the interface definition of our marshallers:

typealias Result<T> = Either<Exception, T>

object Marshallers {

    fun <T : Any>convertToJson(m: T, ev: JsonMarshaller<T>): JsonElement = ev.run { m.toJson() }

    interface JsonMarshaller<T : Any> {
        fun T.toJson(): JsonElement

        companion object {}
    }
}

object Unmarshallers {

    fun <T : Any>convertFromJson(j: String, ev: JsonMarshaller<T>): Result<T> = ev.run { fromJson(j) }

    interface JsonMarshaller<T : Any> {
        fun fromJson(j: String): Result<T>

        companion object {}
    }

}

Here you can see that we’ve defined an interface that can handle marshalling a T to a JsonElement and one that does the opposite. Unmarshal an incoming String to a Either<T>. We’ve also added a helper method that takes an instance of a typeclass and calls the relevant function. Before we look at implementations of these typeclasses let’s take a quick look at a very simple model.

object Model {
    data class Product(val productId: String, val description: String) {
        companion object {}
    }

    data class OrderLine(val lineId: Int, val quantity: Int, val product: Product) {
        companion object {}
    }

    data class Order(val orderId: String, val orderLines: List<OrderLine>) {
        companion object {}
    }
}

Create typeclass instances for the marshallers

The first thing we’re going to do is define the marhsallers. The marshaller will take a T and convert it to a JSONElement. With Kotlin, creating this is pretty simple. Let’s start with the most simple approach. A quick note on this code and the code later on in the article. This is purely to demonstrate how typeclasses work in Kotlin. In real code we would do this differently (especially use a different JSON library), so don’t look to much at the something convoluted code in the marshallers and the unmarshallers.

    val orderLineMarshaller = object : JsonMarshaller<Model.OrderLine> {
        override fun Model.OrderLine.toJson(): JsonElement {
            val orderLine = gson.toJsonTree(this).asJsonObject
            orderLine.add("product", Marshallers.convertToJson(this.product, MarshallerInstances.productMarshaller))
            return orderLine
        }
    }

    val productMarshaller = object : JsonMarshaller<Model.Product> {
        override fun Model.Product.toJson(): JsonElement = gson.toJsonTree(this)
    }
    
    val orderMarshaller = object : JsonMarshaller<Model.Order> {
        override fun Model.Order.toJson(): JsonElement {
            val orderLines = this.orderLines.map { Marshallers.convertToJson(it, orderLineMarshaller) }
            val withoutOrderLines = this.copy(orderLines = listOf())
            val json = gson.toJsonTree(withoutOrderLines)

            val f = JsonArray()
            val acc = orderLines.foldLeft(f) { res, el -> res.add(el); res}
            val result = json.asJsonObject
            result.remove("orderLines")
            result.add("customorderlines", acc)

            return result
        }
    }

In this code fragment you can see that we create a numbr of instances of the JsonMarshaller interface. And use the Marshallers.converToJson function to convert the nested objects to JSON. We could of course also have used the gson.toJsonTree(this) call everywhere, but that wouldn’t have allowed us to easily customize the way the various objects are marshalled.

Now lets set up a simple test to see what is happening when we run this code:

   describe("The product marshaller instance") {

        val product = Model.Product("product", "description")

        it("should marshall an product to a JSON value") {
            val result = MarshallerInstances.productMarshaller.run {
                product.toJson().toString()
            }

            result `should be equal to` """{"productId":"product","description":"description"}"""
        }
    }


    describe("The orderline marshaller instance") {

        val orderLine = OrderLine(1, 2, Model.Product("", ""))

        it("should marshall an orderline to a JSON value") {
            val result = MarshallerInstances.orderLineMarshaller.run {
                orderLine.toJson().toString()
            }

            result `should be equal to` """{"lineId":1,"quantity":2,"product":{"productId":"","description":""}}"""
        }
    }

    describe("The order marshaller instance") {

        val orderLines = listOf(OrderLine(1,2, Model.Product("", "")), OrderLine(2,3, Model.Product("", "")))
        val order = Order("orderId", orderLines)

        it("should marshall an order to a JSON value") {
            val result = MarshallerInstances.orderMarshaller.run {
                order.toJson().toString()
            }

            result `should be equal to` """{"orderId":"orderId","customorderlines":[{"lineId":1,"quantity":2,"product":{"productId":"","description":""}},{"lineId":2,"quantity":3,"product":{"productId":"","description":""}}]}"""
        }
    }

All these tests pass, since we’re just calling them directly. We can also use the function defined in the Marshallers object:

fun <T : Any>convertToJson(m: T, ev: JsonMarshaller<T>): JsonElement = ev.run { m.toJson() }
...
val orderLine = OrderLine(1, 2, Model.Product("", ""))
Marshallers.convertToJson(orderline, MarshallerInstances.orderLineMarshaller)

The big advantage of calling it like this, is that the compiler checks for us that we’ve used the correct typeclass instance. If we try to call this with a typeclass instance of the incorrect type, the compiler starts complaining:

val orderLines = listOf(OrderLine(1,2, Model.Product("", "")), OrderLine(2,3, Model.Product("", "")))
val order = Order("orderId", orderLines)
Marshallers.convertToJson(order, MarshallerInstances.orderLineMarshaller)

This will fail:

e: fskotlin/src/test/kotlin/org/smartjava/typeclasses/MarshallersSpecTest.kt: (51, 21): Type inference failed: Cannot infer type parameter T in fun <T : Any> convertToJson(m: T, ev: Marshallers.JsonMarshaller<T>): JsonElement
None of the following substitutions
(Model.OrderLine,Marshallers.JsonMarshaller<Model.OrderLine>)
(Model.Order,Marshallers.JsonMarshaller<Model.Order>)
(Any,Marshallers.JsonMarshaller<Any>)
can be applied to
(Model.Order,Marshallers.JsonMarshaller<Model.OrderLine>)

As you might have noticed, we have to pass in the specific instance of the typeclass that we want to use. I come from a scala background and am used to passing in typeclasses as implicit parameters (or use a context bounded type parameter). With that approach it is enough for the typeclass to be (implicitly) in scope, so we don’t have to explicitly pass it into the function.
For Kotlin there is a proposal (KEEP-87), which proposes something similar. There is also a reference implementation available, that already allows you to play around with it. So in a future article I’ll do the same as in this article, but then with that implementation.

Now let’s also quickly implement the unmarshallers to get back from JSON to Kotlin.

Create typeclass instances for the marshallers

For a very naive implementation of the unmarshallers we can create something like this:

object UnmarshallerInstances {

    val gson = Gson()
    val parser = JsonParser()
    
    val productUnmarshaller = object : JsonUnmarshaller<Model.Product> {
        override fun fromJson(j: String): Result<Model.Product> = try {
            Either.right(gson.fromJson(j, Model.Product::class.java))
        } catch (e: Throwable){
            Either.left(e)
        }
    }
    
    val orderLineUnmarshaller = object : JsonUnmarshaller<Model.OrderLine> {
        override fun fromJson(j: String): Result<Model.OrderLine> = try {
            // first use the productUnmarshaller to get a Result<Product>
            val jsonObject = parser.parse(j).asJsonObject
            val jsonProduct = jsonObject.get("product")
            val product = productUnmarshaller.fromJson(jsonProduct.toString())

            // if product is right, convert it to a product, else we get the error.
            product.map{ p -> Model.OrderLine(jsonObject.get("lineId").asInt, jsonObject.get("quantity").asInt, p)}
        } catch (e: Throwable){
            Either.left(e)
        }
    }

    val orderUnmarshaller = object : JsonUnmarshaller<Model.Order> {
        override fun fromJson(j: String): Result<Model.Order> = try {

            val jsonObject = parser.parse(j).asJsonObject
            val jsonOrderLines = jsonObject.get("customorderlines").asJsonArray

            // convert using the correct unmarsahller
            val orderLines = jsonOrderLines.map { ol -> orderLineUnmarshaller.fromJson(ol)}

            // now we've got a List<Result<OrderLine>>. We'll reduce it to a Result<List<OrderLine>
            // so that we only continue of all succeed. Missing pattern matching and scala collections here.
            val rs = Either.Right(listOf<Model.OrderLine>())
            val orderLinesK = orderLines.foldLeft<Result<Model.OrderLine>, Result<List<Model.OrderLine>>>(rs) { res, ol ->
               when (res) {
                    is Either.Left -> res
                    is Either.Right -> when(ol) {
                        is Either.Left -> ol                               // set the error
                        is Either.Right -> Either.right(res.b.plus(ol.b))
                    }
                }
            }

            // and finally return the unmarshalled object
            orderLinesK.map{ ols -> Model.Order(jsonObject.get("orderId").asString, ols)}
        } catch (e: Throwable){
            Either.left(e)
        }
    }
}

Without going into too much detail here. You can see that we do some custom JSON unmarshalling here to convert our custom generated JSON back to our data classes. The productUnmarshaller is really straightforward, and we just use the standard gson unmarshall functionality. For the orderLineUnmarshaller we reuse the productUnmarshaller and use the map function to creae a OrderLine is the Product was parsed successfully. And for the complete order, we reuse the orderLineUnmarshaller to convert the incoming data to a List<Result<OrderLines>>. We fold this into a Result<List<OrderLines> failing if one of the orderLines is a Left. Finally we use this result to create the Ordere. At this point we also see some limitations in the type interference of Kotlin. We need to make explicitly pass in the types for the foldLeft function, so that the Kotlin compiler understands what is happening.

For completeness sake lets also create some tests for this, so that we know that the marshalles actually do what they’re supposed to do.

object UnmarshallersSpecTest: Spek({

    describe("The product unmarshaller instance") {

        val productJson = """{"productId":"product","description":"description"}"""
        val product = Model.Product("product", "description")

        it("should marshall an product to a JSON value") {

            val result = UnmarshallerInstances.productUnmarshaller.run {
                fromJson(productJson)
            }

            result.isRight() `should be` true
            result.map {
                it `should equal` product
            }
        }

        it("should return a Left when JSON is invalid") {
            val result = UnmarshallerInstances.productUnmarshaller.run {
                fromJson("invalid")
            }

            result.isLeft() `should be` true
        }
    }


    describe("The orderline unmarshaller instance") {

        val orderLine = OrderLine(1, 2, Model.Product("", ""))
        val orderLineJson = """{"lineId":1,"quantity":2,"product":{"productId":"","description":""}}"""

        it("should marshall an orderline to a JSON value") {
            val result = UnmarshallerInstances.orderLineUnmarshaller.run {
                fromJson(orderLineJson)
            }

            result.isRight() `should be` true
            result.map {
                it `should equal` orderLine
            }
        }

        it("should return a Left when JSON is invalid") {
            val result = UnmarshallerInstances.orderLineUnmarshaller.run {
                fromJson("invalid")
            }

            result.isLeft() `should be` true
        }
    }

    describe("The order unmarshaller instance") {

        val orderLines = listOf(OrderLine(1,2, Model.Product("", "")), OrderLine(2,3, Model.Product("", "")))
        val order = Order("orderId", orderLines)
        val orderJson = """{"orderId":"orderId","customorderlines":[{"lineId":1,"quantity":2,"product":{"productId":"","description":""}},{"lineId":2,"quantity":3,"product":{"productId":"","description":""}}]}"""

        it("should marshall an order to a JSON value") {
            val result = UnmarshallerInstances.orderUnmarshaller.run {
                fromJson(orderJson)
            }

            result.isRight() `should be` true
            result.map {
                it `should equal` order
            }
        }

        it("should return a Left when JSON is invalid") {
            val result = UnmarshallerInstances.orderUnmarshaller.run {
                fromJson("invalid")
            }

            result.isLeft() `should be` true
        }
    }
})

Add this point we’ve seen a little bit of the Arrow functionality already. We’ve used the Either typeclass, and also used the foldLeft and map extensions Arrow provides on top of the base classes. In the next section we’ll look a bit closer at using the typeclasses directly and use a custom annotation from Kotlin, that helps in making typeclass instances easier.

Use Arrow functionality: extension annotation

In the previous code fragments we’ve seen that we can create type classes by implementing an interface and assigning it to a value which we then can use directly. If you’ve only got a small number of typeclasses to create this works nice, but you do have to create the wrappers and assign values yourself. With the @extension annotation, Arrow generates code which makes it easy for you to get the instance for a specific type.

The following example shows how to use the @extension annotation for our Unmarshallers

    @extension
    interface OrderLineUnmarshallerInstance : JsonUnmarshaller<Model.OrderLine> {
        override fun fromJson(j: String): Result<Model.OrderLine> = try {
            // first use the productUnmarshaller to get a Result<Product>
            val jsonObject = parser.parse(j).asJsonObject
            val jsonProduct = jsonObject.get("product")
            val product = productUnmarshaller.fromJson(jsonProduct.toString())

            // if product is right, convert it to a product, else we get the error.
            product.map{ p -> Model.OrderLine(jsonObject.get("lineId").asInt, jsonObject.get("quantity").asInt, p)}
        } catch (e: Throwable){
            Either.left(e)
        }
    }

As you can see not that much has changed. The main thing that has changed is that we now define an interface, and not an val or fun. What Arrow does, it will generate code that looks like this:

package org.smartjava.fskotlin.orderline.jsonUnmarshaller

import arrow.core.Either
import kotlin.String
import kotlin.Suppress
import kotlin.Throwable
import kotlin.jvm.JvmName
import org.smartjava.fskotlin.Model.OrderLine.Companion
import org.smartjava.fskotlin.OrderLine
import org.smartjava.fskotlin.OrderLineUnmarshallerInstance

@JvmName("fromJson")
@Suppress(
        "UNCHECKED_CAST",
        "USELESS_CAST",
        "EXTENSION_SHADOWED_BY_MEMBER",
        "UNUSED_PARAMETER"
)
fun fromJson(j: String): Either<Throwable, OrderLine> = org.smartjava.fskotlin.Model.OrderLine
   .jsonUnmarshaller()
   .fromJson(j) as arrow.core.Either<kotlin.Throwable, org.smartjava.fskotlin.OrderLine>

fun Companion.jsonUnmarshaller(): OrderLineUnmarshallerInstance = object : org.smartjava.fskotlin.OrderLineUnmarshallerInstance {  }

As you can see this will create an OrderLineUnmarshallerInstance on the companion object of the OrderLine class, so we can easily access it, and create a helper function for easy access to the fromJson function. Throughout the Arrow code base this is used extensively, which is a good thing, to make sure that all the typeclasses follow the same pattern. For your own projects, I don’t think you should need this, and it’s probably easier and more straightforward to just define the typeclasses directly and assign them to a fun or a val.

Use Arrow functionality: Foldable typeclass

Before ending this article, I want to clean up a couple of code fragments by using some other arrow typeclasses. The first one is the orderMarshaller:

    val orderMarshaller = object : JsonMarshaller<Model.Order> {
        override fun Model.Order.toJson(): JsonElement {
            val orderLines = this.orderLines.map { Marshallers.convertToJson(it, orderLineMarshaller) }
            val withoutOrderLines = this.copy(orderLines = listOf())
            val json = gson.toJsonTree(withoutOrderLines)

            val f = JsonArray()
            val acc = orderLines.foldLeft(f) { res, el -> res.add(el); res}
            val result = json.asJsonObject
            result.remove("orderLines")
            result.add("customorderlines", acc)

            return result
        }
    }

While this works, it isn’t the best way to do this. We first have to explicitly map the orderLines to a List<JsonElement>, we can’t use this list directly, but have to convert it again to a JsonArray, before we can use it. What would be nice is if we could have a JsonMarshaller which would be able to automatically marshall a list or set of T to a JsonArray. If we look at the previous code what we need to do is some mapping from T to a JsonElement and some folding. To go from T to JsonElement we’ve already got out JsonMarshaller, and Arrow provides a typeclass called Foldable that allow us to fold over a specific container. With these two typeclasses we can create a JsonMarshaller instance that is able to create the JsonArray for arbitrary foldable containers like this:

    /**
     * For the type of Kind<F, T> e.g ListK<T> we can automatically fold them using the provided typeclass. What
     * we need to know is evidence that it's foldable, and evidence on how to apply the marshaller to the embedded T
     * elements.
     */
    fun <F, T: Any>foldableToJsonArrayMarshaller(fev: Foldable<F>, ev: JsonMarshaller<T>) = object: JsonMarshaller<Kind<F, T>> {
        override fun Kind<F, T>.toJson(): JsonElement = fev.run {
            foldLeft(JsonArray()) { b, a ->
                b.add(ev.run { a.toJson() })
                b
            }
        }
    }

Here we use the provided foldable, to invoke foldLeft on the container, and the provided marshaller to convert the T elements contained in the foldable to a JsonElement. All the converted Ts are aggregated into a JsonArray, which is returned. Ignore the Kind<F, T> stuff for now, since that is something for a different article. It is enough to know for now, that this is the way Arrow allows us to use higher kinded types for extensions. With this generic marshaller, we can now also change the orderMarshaller to something like this:

    val orderMarshaller = object : JsonMarshaller<Model.Order> {
        override fun Model.Order.toJson(): JsonElement {
            val json = gson.toJsonTree(this.copy(orderLines = listOf())).asJsonObject
            val orderLinesJson = MarshallerInstances.foldableToJsonArrayMarshaller(foldable(), orderLineMarshaller).run { ListK(orderLines).toJson() }

            json.remove("orderLines")
            json.add("customorderlines", orderLinesJson)

            return json
        }
    }

And you can see that we use the foldableToJsonArrayMarshaller and pass in a foldable() which can be retrieved from the List companion object, and our marshaller. The rest is done in the same way as for the other typeclasses. This makes our code much more clean, and if we have other containers with T elements, we can reuse this foldableToJsonArrayMarshaller marshaller.

Conclusion

All in all it isn’t that hard to use and create typeclasses in Kotlin. With Arrow we get a lot of functionality, that makes functional programming, working with monads and typeclasses much easier. What you do see is that in certain parts the standard library of Kotlin is limited. Pattern matching is limited, and the lack of implicit arguments in functions makes using typeclasses kind of intrusive. Besides that, the lack of a good API on top of immutable collections in Kotlin makes me miss standard functionality in other functional languages.

But, Arrow in itself looks great. While it takes some effort to get my head in the right place, the provided type and data classes work as expected (so far) and I’ll continue experimenting with the various parts to see how Arrow and Kotlin can work together.

Updated: