Search code examples
kotlinkotlin-genericskotlin-reified-type-parameters

Passing only types marked as reified to Kotlin generic function


Let's say I have the following code:

open class Fruit
class Apple : Fruit()
open class Juice<T : Fruit>
class AppleJuice : Juice<Apple>()

fun <F : Fruit, J : Juice<F>> makeJuice(juiceClass : Class<J>, fruit : F) : J {}

I call the function like this:

val appleJuice : AppleJuice = makeJuice(AppleJuice::class.java, Apple())

But instead of passing a class object I would like to pass AppleJuice as a type:

val appleJuice : AppleJuice = makeJuice<AppleJuice>(Apple())

I have refactored my function to inline with reified:

inline fun <F : Fruit, reified J : Juice<F>> makeJuice(fruit : F) : J {}

But now I have to specify both types:

val appleJuice : AppleJuice = makeJuice<Apple, AppleJuice>(Apple())

In theory, Apple type shouldn't be needed because it's already known from AppleJuice type. Is it somehow possible to get rid of passing unnecessary types and pass only those maked as reified?


Solution

  • The main problem I see with your solution is, that you are asking for 2 generic types on your makeJuice-method. Both F and J need to be given to the function. While it is obvious for you (and anyone looking at the method), I think it might not be that obvious during runtime when the generic types are erased (but that is now mainly a guess here).

    If you do not mind that the fruits you are passing are not really matching subtypes of the juice you are expecting then the following may be something for you:

    inline fun <reified J : Juice<out Fruit>> makeJuice(fruit : Fruit) : J = TODO()
    

    If however you want to ensure that AppleJuice can only be constructed with Apples, then I can only think of solutions similar to the following:

    1. adding the makeJuice to the Fruit classes, e.g.

      abstract class Fruit {
        abstract fun makeJuice() : Juice<out Fruit>
      }
      // and subclasses:
      class Apple : Fruit() {
        override fun makeJuice(): AppleJuice = TODO()
      }
      
    2. adding a makeJuice (/makeFrom?) to the Juice classes, e.g.

      open class Juice<T : Fruit> {
        fun makeFrom(fruit : T) { TODO() }
      }
      
    3. adding any other intermediate object so that you do not require 2 generic types at once, e.g.

      class JuiceMaker<F : Fruit>(val fruit : F) {
        inline fun <reified J : Juice<F>> makeJuice() : J = TODO()
      }
      fun <F : Fruit> using(fruit : F) = JuiceMaker(fruit)
      

      and calling it with

      using(Apple()).makeJuice<AppleJuice>()
      
    4. variants of the above using extension functions, e.g.

      inline fun <reified J : Juice<out Apple>> Apple.makeJuice() : J = TODO()
      

      but you need to specify it for all types. The following unfortunately will not work:

      inline fun <F : Fruit, reified J : Juice<F>> F.makeJuice() : J = TODO()
      

      as we then have the same problem again... and need to specify <Apple, AppleJuice>.

    But maybe none of these are what you were hoping to get. So if you want to have a single method that handles it all, the third variant is probably your best choice (even though it uses a wrapper).