MaxG
MaxG

Reputation: 1077

Kotlin functional strategy pattern doesn't compile

I'm trying to put several functions in a map. The idea is to have: Map<String, [function]>.

The code is as follows:

class UserIdQueriesHandler {

    val strategy: Map<String, KFunction2<@ParameterName(name = "id") String, @ParameterName(name = "options") Array<Options>, Answer>> =
        mapOf( // the compiler complains here
            "d" to ::debt,
            "p" to ::currentProduct
        )

    fun debt(id: String, options: Array<DebtOptions>): UserDebt = UserDebt(isPresent = true, amount = 0.0)

    fun currentProduct(id: String, options: Array<CurrentProductOptions>): UserProducts = UserProducts(products = emptyList())
}

enum class DebtOptions : Options { BOOL, AMOUNT }

enum class CurrentProductOptions : Options { ALL, PRINT, DIGITAL, ENG, HEB, TM }

data class UserDebt(val isPresent: Boolean, val amount: Double) : Answer
data class UserProducts(val products: List<Int>): Answer

Answer and Options are simple kotlin interfaces:

interface Answer
interface Options

Compiler output:

Type inference failed. Expected type mismatch: 
required:
Map<String, KFunction2<@ParameterName String, @ParameterName Array<Options>, Answer>>
found:
Map<String, KFunction2<@ParameterName String, {[@kotlin.ParameterName] Array  & [@kotlin.ParameterName] Array }, Answer>>

Upvotes: 0

Views: 212

Answers (2)

zsmb13
zsmb13

Reputation: 89548

Since an Array can be both read and written, its type parameter is invariant. This makes it so that you can't assign an Array<DebtOptions> to a variable that has the type of Array<Options>. The former isn't a subtype of the latter, because it would allow you to put other elements in the array that are Options, but not DebtOptions, leading to problems to code that has a reference to this array as an Array<DebtOptions>.

A solution would be to make your functions accept Array<Options>, if you can.

val strategy: Map<String, KFunction2<String, Array<Options>, Answer>> =
        mapOf(
                "d" to ::debt,
                "p" to ::currentProduct
        )

fun debt(id: String, options: Array<Options>): UserDebt = ...

fun currentProduct(id: String, options: Array<Options>): UserProducts = ...

You could combine this with using the nicer functional type instead of a KFunction2:

val strategy: Map<String, (String, Array<Options>) -> Answer> = ...

Upvotes: 1

Alexey Romanov
Alexey Romanov

Reputation: 170745

The type of strategy says functions you put into it can accept any Array<Options> as the second argument, which debt and currentProduct can't.

The simplest workaround would be to change their argument type to Array<Options> (or List<Options>; they probably don't need to mutate it!) and fail at runtime if wrong options are passed or ignore them.

Variance section in the documentation is also relevant.

Upvotes: 1

Related Questions