Reified — abstract made concrete in Kotlin

Text

Backend Kotlin

Uzi Landsmann

Systemutvecklare

 

The Merriam-Webster definition for reify is this:

 

:to consider or represent (something abstract) as a material or concrete thing :to give definite content and form to (a concept or idea)

 

…which makes sense, as it sounds a bit like real-ify, making something that is unreal into something real. So what does that have to do with Kotlin? I’ve seen it used in many code examples but kept thinking this is something for the better skilled or an academic term which I’ll probably never have to use. Until one day, I realised I was totally wrong.

 

In my current project, we have to make a lot of calls between different microservices, implemented using Spring Boot. Since we’re using the RestTemplate Class, someone thought it would be wise to write some methods that would help us skip some of the boilerplate we need because we’re adding some headers to every call. We have therefore created a set of get, post, put and delete methods, of which the get one looks like this (without the header manipulation thingy):

 

fun <R> get(url: String, responseType: Class<R>): R {
    return restTemplate.getForObject(baseUrl + url, responseType)
}

 

We place the methods in a class called GenericClient and use them in our controllers like this:

 

fun getPerson(id: Int): Person {
    val url = "/persons/$id"
    return genericClient.get(url, Person::class.java)
}

Which looks very nice and simple, right? However, things get a little weird when trying to use the same get method to fetch a list:


fun getPersons(): List<Person> {
    val url = "/persons"
    return genericClient.get(url, List::class.java)
           as List<Person>
}

 

If this was Java, we could omit the as List<Person> cast and only get a compilation warning. Kotlin is not as kind and forces us to add the cast, and give us a compilation warning to go with it:

Kotlin: Unchecked cast: List<*> to List<Person>

 

This happens because we can’t specify which type we want to use with the list, only that the return type is a list of something. We’ve accepted that and lived with those warnings for quite some time, until one day I got tired of them and thought, why not try to see if there is a better way.


It was then that I found reified. Turns out, if you declare your generic parameter <reified R: any> (and whatever else is required for that, see below), you don’t even need to send the type as an argument! The get method simply understands it by means of pure magic. Our next version of the get method now looks like this:

internal inline fun <reified R : Any?> get(url: String): R {
val responseType = object : ParameterizedTypeReference<R>() {}
restTemplate.exchange(baseUrl + url, GET, null, 
responseType).body
}

By using the reified keyword and inlining the method, we make the R type accessible inside the method and can use it with the magic of ParameterizedTypeReference class to specify the expected response type. We can now call it from our controller like this:

fun getPersons(): List<Person> {
val url = "/persons"
return genericClient.get(url)
}

Beautiful right? This thing works because our get method can see that our function returns a List<Person> and infers it as the R type. As the Kotlin documentation explains it:

 

You qualified the type parameter with the reified modifier to make it accessible inside the function, almost as if it were a normal class

 

This is where the dictionary definition of reify and the Kotlin implementation of reified meet. It takes something that is abstract, that is, the expected return type of the get method, and makes it concrete and tangible by making the type available for the function.

In other cases, you might have to specify the type of R because it is more difficult to infer:

fun getPersonIds(): List<Int> {
val url = "/persons"
val persons = genericClient.get<List<Person>>(url)
return persons.map { it.id }
}

Since we want to process the result of the get method here, we need to give it more information about the expected result type, by specifying .get<List<Person>>.


There are, however, a few more things to consider when using reified parameters:


Inline

 

A function that uses a reified type must be inlined, that is, prefixed with the keyword inline. What does that mean? Here’s how Baeldung explains inlining:

 

When using inline functions, the compiler inlines the function body. That is, it substitutes the body directly into places where the function gets called

 

Why do we need to inline reified parameters? Here’s how the Kotlin documentation explains it:

 

Normal functions (not marked as inline) cannot have reified parameters. A type that does not have a run-time representation (for example, a non-reified type parameter or a fictitious type like Nothing) cannot be used as an argument for a reified type parameter.

 

…which doesn’t really explains why. This article, however, does a better job.

 

Public

If an inline function is public or protected, it can not use private fields. So the following will simply not compile:

 

inline fun <reified R : Any?> get(url: String): R {
    val responseType = object : ParameterizedTypeReference<R>() {}
    restTemplate.exchange(baseUrl + url, GET, null, 
        responseType).body
}

 

This is because the restTemplate field is private. Building the project gives us the following compilation error:

 

Kotlin: Public-API inline function cannot access non-public-API 'private final val restTemplate: RestTemplate defined in com.whatever.GenericClient'


To remedy this, we add the internal keyword as yet another prefix to the function, which will then allow us to use private fields. This restriction is explained in detail in the Kotlin documentation in case you’re interested why it is so.

 

No Java calls

 

If your code mixes Java and Kotlin, you can’t call a method with the reified parameter from Java. The autocomplete in IntelliJ will even hide those methods:

 

 

As explained in Reification of the erased:

Also, keep in mind that reified functions can not be accessed from Java. Java doesn’t support inlining, and without inlining, generic parameters can not escape being erased by the compiler.

Now that I understand when to use reified, I no longer consider it pure dark magic (although I suspect Kotlin uses a fair amount of voodoo to make things like that work) and find it very practical to use in such cases. I hope this article might shed some light on this great feature.

 

If you want more info about reified and inline functions, have a look at the following links:

 

Fler inspirerande inlägg du inte får missa