notReallyDev
notReallyDev

Reputation: 179

Kotlin type inference for multiple possible types?

I have a function that takes parameter metadata of two possible types: Metadata1 or Metadata2. I'm trying to write a utility function that can be used for both since there is a ton of code reuse. Both of them have a list of DataPoint objects, each of which has name() method.

I thought in my code Kotlin would use type inference to know that dataPoints can only be one of two types that both have DataPoint.name() method, but I'm getting "unresolved reference: name". How can I make this function generic enough to apply to both Metadata1 and Metadata2?

var dataPoints: List<Any>
if (metadata is Metadata1) {
    dataPoints = metadata.metadata1().dataPoints()
} else {
    dataPoints = (metadata as Metadata2).metadata2().dataPoints()
}
if (data.size > 1) {
    textView.test = dataPoints.fold("")
    { x, dataPoint ->
        x + dataPoint.name() + " \n" // unresolved reference: name
    }
}

Upvotes: 0

Views: 1738

Answers (1)

Trevor
Trevor

Reputation: 1451

This isn't unique to Kotlin, statically typed OOP languages work like this.

Your dataPoints is of type List<Any> and Any does not have a name() function. You're not showing a lot of code so I can't tell what kind of objects you have.

This is about run-time vs compile-time. The compiler can't predict what types you're going to put into your List<Any> at runtime and so the only functions you can call on its members are functions that belong to the Any class. Imagine if that list contained an object that didn't have a name() function. If the compiler allowed you to call name() then you'd have a run-time crash. This is why you get a compiler-time error when you try.

If you have two different types of objects that goes in the list, one way would be to make an interface that they both implement with shared methods in the interface. Which would look something like this:

interface Name {
    fun name()
}

Change dataPoints to List<Name>, have your data classes implement that, and now dataPoint.name() compiles because the only objects allowed in the list are objects of type Name with a name() function.

var dataPoints: List<Name>
if (metadata is Metadata1) {
    dataPoints = metadata.metadata1().dataPoints()
} else {
    dataPoints = (metadata as Metadata2).metadata2().dataPoints()
}
if (data.size > 1) {
    textView.test = dataPoints.fold("")
    { x, dataPoint ->
        x + dataPoint.name() + " \n" // Compiles now
    }
}

You have a similar problem with your Metadata1 and Metadata2 classes, they should probably implement an interface or extend a super class.

Upvotes: 1

Related Questions