Introduction

Did you ever wonder how a function like apply or let works? How about we talk about writing functions like these yourself?

This time I would really like to take a deeper dive into Kotlin, and show you the elegance and simplicity of the design of generics, and how you can do some very cool stuff with scoping. So, did you ever think about how to implement your own apply, or let function? If so, read on and I'll show you some of the awesomeness of Kotlin. But first let's talk about generics.

Note: I am not encouraging you to replace the standard functions with your own, I'm just going to show how you could implement them yourself.

Generics

A lot in this chapter has been taken from the Kotlin documentation. All code is attributed where needed.

Generics in Kotlin are a lot like generics in Java. A simple List can be declared like this:

              
val list: List<String>
            

As you can see, nothing too fancy. It really looks a lot like Java. We can define a list, and add a type argument to it. Something that's really nice in Kotlin is how the compiler is able to infer the generic type argument. For example, given this class:

              
class Box<T>(val t: T) 
            

The following code will infer its type correctly as Box<Int>:

              
val box = Box(1)
            

Example taken from Kotlin website

When inspecting the type of box you will see that the IDE will tell you it's of type Box<Int>. One other important thing to notice is Variance.
Read more about variance it in the documentation, I'll only give a brief example

Java's generics

So Java's generic type are what they call invariant. It means the following is not allowed:

              
// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!! The cause of the upcoming problem sits here. Java prohibits this!
objs.add(1); // Here we put an Integer into a list of Strings
String s = strs.get(0); // !!! ClassCastException: Cannot cast Integer to String
            

Taken from the documentation

To fix all this, they came up with wildcards. For example Java's Collections.addAll is declared like this:

              
void addAll(Collection<? extends E> items);
            

This is not the most readable declaration, and it can get really complicated when we need to deal with multiple Type parameters with complex declarations.

Kotlin's generics

Different from the approach we've seen in the Java examples, Kotlin allows us to define the variance in the code. For example the following is allowed, and safe in Kotlin:

              
interface Source<out T> {
    fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // This is OK, since T is an out-parameter
    // ...
}
            

Taken from here

Java has no equivalent that can be defined without compiler warnings. The out keyword tells us that T is only returned from members of this class (out). According to the documentation:

The general rule is: when a type parameter T of a class C is declared out, it may occur only in out-position in the members of C, but in return C<Base> can safely be a supertype of C<Derived>.

As you might expect, Kotlin also has the in keyword. With in, the type parameter is made contravariant. It can only be consumed, and not produced. A good example is this use of Comparable:

              
interface Comparable<in T> {
    operator fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
    // Thus, we can assign x to a variable of type Comparable<Double>
    val y: Comparable<Double> = x // OK!
}
            

Taken from here

Besides in and out Kotlin also has the star projection (*). I'm not going into to much details here. It is basically a shorthand for Nothing and Any. The documentation says the following about the star projection; given declaration interface Function<in T, out U> we can imagine the following star-projections:

Notation Meaning
Function<*, String>; Function<in Nothing, String>;
Function<Int, *> Function<Int, out Any?>;
Function<*, *> Function<in Nothing, out Any?>
Read more about using the star projection here

Reified

I'm also quickly going to mention the reified keyword. In Java it is common to sometimes pass a class as an argument to a method, to get its type and to be able to cast it. So you would get a function call that looks something like this:

              
view.findParentOfType(FrameLayout::class.java)
            

The definition of this function would be

              
fun <T> View.findParentOfType(clazz: Class<T>): T?
            

With the reified keyword we can convert that call to be something like this:

              
view.findParentOfType<FrameLayout>()
            

Now that reads a lot easier right? The definition of that function would be the following:

              
inline fun <reified T> View.findParentOfType(): T?
            

So with reified you can make your code a lot more readable and to the point.

Read more about reified

Type aliases

One final thing I would like to mention before really diving in, is the ability to use type aliases. Under certain circumstances it will make it easier to deal with generic types, and make it easier to pass them around. In effect removing cognitive load. For example:

              
// Define StringList as a reference to List<String>
typealias StringList = List<String>

// Define Predicate<T> as a reference to (T) -> Boolean
typealias Predicate<T> = (T) -> Boolean

// with type alias
fun foo(p: Predicate<Int>) = p(42)
// without type alias
fun foo(p: (Int) -> Boolean) = p(42)
            

Example partially taken from here

In the first example we create an alias for the type List<String>. The compiler will expand this type, so no real class will be added to your code. So instead of writing List<String> we can now write StringList as well.

The second sample is a bit more advanced. This creates an alias for the type (T) -> Boolean, actually is creates an alias for any function that accepts a single parameter and returns a Boolean. As you can see in the example the first foo function is more readable because there is no complex declaration for its argument type.

Now that we got the basics covered, let's move to the good stuff.

Generic extensions

One of the thing we all love about Kotlin are extension functions. What may not be so obvious is that you can also define an extension function for a generic type. For example we could say:

              
fun <T> T.myFunction()
            

Above function is not very useful, but we just added an extension function named myFunction to all types. We can do better, let's do something with that type argument. For example, consider this function definition:

              
fun <T, R> T.doSomething(block: (T) -> Unit) {
    block(this)
}
            

This definitions actually creates a function that we can call almost like we can call the let function. We could use this function like this:

              
nullable: Int?
...
nullable?.doSomething { it.something() } 
            

The block: (T) -> Unit specifies that block is a function that accepts a parameter of type T and returns Unit. T is the same type as the Type we declare the function on. As you can see in the implementation, we just call the block with this as its only argument.

Now, let's make it more interesting and define something that behaves like the let. We need to add a new type parameter to define the return type, and update the block, and the function to return this type. Our own let function will look like this:

              
fun <T, R> T.myLet(block: (T) -> R) : R {
    return block(this)
}
            

But we can do better, let's remove the unneeded overhead:

              
fun <T, R> T.myLet(block: (T) -> R) = block(this) 
            

Now this behaves like the normal let function!

Let's take a closer look. Like before, block is a function, that receives an argument of type T. But now it returns something of type R. The function let also returns the same type R. So we could call it like this:
val myInt = myString?.myLet { it.asInt } ?: 0. This function behaves exactly the same as the standard let function.

Also

So to create our own also function, we could write something like this:

              
fun <T> T.myAlso(block: (T) -> Unit) : T {
    block(this)
    return this
}
            

As you can see, myAlso just takes some object, and then executes the block on it. Finally returning the object of type T itself.

How about run?

That's very cool right? So let's take this another step. How can we create something that has “this” as its receiver? You can do this by defining the block as a function literal. This would make block behave like an anonymous extension, or in Kotlin terms the defining type would be the receiver. See below:

              
fun <T, R> T.myRun(block: T.() -> R) = return block() 
            

The above T.() -> R defines this function literal. T is the receiver and R is the result. The return type of myRun is R, this is inferred from the return type of block. Above function behaves exactly like Kotlin's run function.

Conclusion

Kotlin has a very powerful, and yet simple to understand system of generics. I personally like the extension functions and the function literals a lot. They really help breaking down complex declarations into simple and elegant generic functions.
I hope you enjoyed this post!

Cheers, Nick 🍺