Local Types

Not only can we nest functions inside of functions, but we can also create local classes, interfaces, and other types as well!

Scenario: Perils of Pair Programming

Sometimes, we need some sort of data structure to represent an interim result.

Android developers using RxJava in Kotlin run into this scenario a lot, particularly with operators like zip(). zip() says “do this list of operations in parallel, then combine their results and give that to me”. zip() not only takes the operations (e.g., a pair of Single objects), but also some lambda expression or other function type that combines the results of each Single into a single object result:

Single.zip(userAuthCall, serverStatusCall) { userAuthResult, serverStatusResult ->
  TODO("ummmm... combine these... somehow...")
}

A cheap and easy solution is to use a Pair, which wraps an arbitrary pair of objects. The top-level to() inline function can create a Pair:

Single.zip(userAuthCall, serverStatusCall) { userAuthResult, serverStatusResult ->
  userAuthResult to serverStatusResult
}

Something later that subscribes to get the results will get the Pair:

Single.zip(userAuthCall, serverStatusCall) { userAuthResult, serverStatusResult ->
  userAuthResult to serverStatusResult
}
  .subscribe { pairOfResults ->
    // TODO something cool
  }

However, Pair just has first and second properties to get the wrapped values. first and second are fine property names, but they are very general and do not have any real meaning in our code.

One option is to use a destructuring declaration to break the Pair back apart into two variables:

Single.zip(userAuthCall, serverStatusCall) { userAuthResult, serverStatusResult ->
  userAuthResult to serverStatusResult
}
  .subscribe { (userAuthResult, serverStatusResult) ->
    // TODO something cool
  }

Another option would be a custom data class instead of Pair, so you can use property names that have greater meaning:

data class OpResults(val userAuth: UserAuth, val serverStatus: ServerStatus)

Single.zip(userAuthCall, serverStatusCall) { userAuthResult, serverStatusResult ->
  OpResults(userAuth = userAuthResult, serverStatus = serverStatusResult)
}
  .subscribe { results ->
    // TODO something cool with results.userAuth and results.serverStatus
  }

The question then becomes: where does that data class reside? If it has no value outside the boundaries of one function, does it make sense for this to be a top-level class or a nested class?

With Kotlin, you have another answer: declare the data class inside the function that needs it:

fun loadStuff() {
  data class OpResults(val userAuth: UserAuth, val serverStatus: ServerStatus)

  Single.zip(userAuthCall, serverStatusCall) { userAuthResult, serverStatusResult ->
    OpResults(userAuth = userAuthResult, serverStatus = serverStatusResult)
  }
    .subscribe { results ->
      // TODO something cool with results.userAuth and results.serverStatus
    }
}

This is a bit more verbose than a Pair. However, ideally, if you use the Pair, you would have comments in the code explaining what first and second are. If you are going to bother explaining it via comments… perhaps you could explain it via code instead.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.