Range validator for multiple types I have an existing overloaded set of functions that I cannot change and need to be tested:

object Validator {
    fun validateRange(min: Int, max: Int): Boolean = true
    fun validateRange(min: Long, max: Long): Boolean = true
    fun validateRange(min: Double, max: Double): Boolean = true
    fun validateRange(min: String, max: String): Boolean = true

    fun validateRangeInput(min: Int, max: Int, input: Int): Boolean = true
    fun validateRangeInput(min: Long, max: Long, input: Long): Boolean = true
    fun validateRangeInput(min: Double, max: Double, input: Double): Boolean = true
    fun validateRangeInput(min: String, max: String, input: String): Boolean = true
}


The test data for validateRange() contain each of the various types. Here is a sample of the input data:

val inputs = listOf(
        Pair("1", "1"),
        Pair(1, 1),
        Pair(1L, 1L),
        Pair(1.0, 1.0)
)


Of course, I cannot simply do this:

inputs.forEach { (min, max) -&gt; Validator.validateRange(min, max) }


My current solution implements a helper using generics and a when block with the is operator and smart casting:

fun &lt;T&gt; validateRangeHelper(min: T, max: T): Boolean {
    return when {
        min is Int &amp;&amp; max is Int -&gt; Validator.validateRange(min, max)
        min is Long &amp;&amp; max is Long -&gt; Validator.validateRange(min, max)
        min is Double &amp;&amp; max is Double -&gt; Validator.validateRange(min, max)
        min is String &amp;&amp; max is String -&gt; Validator.validateRange(min, max)
        else -&gt; throw UnsupportedOperationException()
    }
}

inputs.forEach { (a, b) -&gt; validateRangeHelper(a, b) }


This approach grows cumbersome with each additional argument, such as for the validateRangeInput() functions.

fun &lt;T&gt; validateRangeHelper(min: T, max: T, input: T): Boolean {
    return when {
        min is Int &amp;&amp; max is Int &amp;&amp; input is Int -&gt; Validator.validateRangeInput(min, max, input)
        min is Long &amp;&amp; max is Long &amp;&amp; input is Long -&gt; Validator.validateRangeInput(min, max, input)
        min is Double &amp;&amp; max is Double &amp;&amp; input is Double -&gt; Validator.validateRangeInput(min, max, input)
        min is String &amp;&amp; max is String &amp;&amp; input is String -&gt; Validator.validateRangeInput(min, max, input)
        else -&gt; throw UnsupportedOperationException()
    }
}


Is there a way I can improve the helper or avoid it all together without changing Validator?
Definitely not one of my proudest answers...
You can delegate the work of casting to another function

fun &lt;T&gt; Pair&lt;*,*&gt;.validate2(lambda: (T, T)-&gt;Boolean) = lambda(first as T, second as T)


In your validateHelperclass you can call this function:

fun &lt;T&gt; validateRangeHelper(pair: Pair&lt;T,T&gt;): Boolean = when(pair.first) {
    is Int -&gt; pair.validate3&lt;Int&gt;(Validator::validateRange)
    is Double -&gt; pair.validate3&lt;Double&gt;(Validator::validateRange)
    is Long -&gt; pair.validate3&lt;Long&gt;(Validator::validateRange)
    is String -&gt; pair.validate3&lt;String&gt;(Validator::validateRange)
    else -&gt; throw UnsupportedOperationException()
}


(I called it validate2, as I created a custom class which has more than 2 parameters, such that I could test both the methods, based on  if i called validate2 or validate3...)

typeunsafe

//just to be able to get three
class TestValues&lt;T : Any&gt;(
    val min: T,
    val max: T,
    val input: T
)


I search the functions with a certain name and parameter count. I extracted it from a function, but of course you can type the name yourself. Note, you have to import kotlin.reflect.KFunction2.

inline fun &lt;reified R : Any&gt; createTester2WithInt(obj: R, f: KFunction2&lt;Int, Int, Boolean&gt;) =
    Tester2(
        obj,
        R::class.findMethodsWithType(f.name, 2)
    )

inline fun &lt;reified R : Any&gt; createTester3WithInt(obj: R, f: KFunction3&lt;Int, Int, Int, Boolean&gt;) =
    Tester3(
        obj,
        R::class.findMethodsWithType(f.name, 3)
    )

open class Tester(
    private val obj: Any,
    private val map: Map&lt;KClass&lt;out Any&gt;, KFunction&lt;Any?&gt;&gt;
) {
    @PublishedApi // means you shouldn't call it by yourself
    internal inline fun &lt;reified T, reified S : Any&gt; call(vararg args: S, clazz: KClass&lt;out S&gt;): T {
        val method = map[clazz] ?: throw java.lang.UnsupportedOperationException()
        return method.call(obj, *args) as T
    }
}

class Tester2(obj: Any, map: Map&lt;KClass&lt;out Any&gt;, KFunction&lt;Any?&gt;&gt;) : Tester(obj, map) {
    inline fun &lt;reified T, reified S : Any&gt; test(first: S, second: S): T = call(first, second, clazz = first::class)
}

class Tester3(obj: Any, map: Map&lt;KClass&lt;out Any&gt;, KFunction&lt;Any?&gt;&gt;) : Tester(obj, map) {
    inline fun &lt;reified T, reified S : Any&gt; test(first: S, second: S, third: S): T = call(first, second, third, clazz = first::class)
}

fun &lt;R : Any&gt; KClass&lt;out R&gt;.findMethodsWithType(
    name: String,
    parameterCount: Int
) = memberFunctions.asSequence()
    .filter { it.name == name }
    //the returnType is also a parameter
    .filter { it.parameters.size == parameterCount + 1 }
    .filter { it.returnType.jvmErasure == Boolean::class }
    .filter {
        val paramTypes = it.javaMethod!!.parameterTypes
        paramTypes[0] == paramTypes[1]
    }.associateBy { it.javaMethod!!.parameterTypes[0].kotlin }


fun create2Pred(pred: (Any, Any) -&gt; Boolean) = pred


fun main() {
    val validator2 = createTester2WithInt&lt;Validator&gt;(Validator, Validator::validateRange)
    val validator3 = createTester3WithInt&lt;Validator&gt;(Validator, Validator::validateRangeInput)
    val list = listOf(
        TestValues(1, 1, 1),
        TestValues(1.0, 1.0, 1.0),
        TestValues("1", "1", "1")
    ).forEach {
        val a: Boolean = validator2.test(it.min, it.max)
        val b: Boolean = validator3.test(it.min, it.max, it.input)
        println("$a and $b")
    }
}


object Validator {
    fun validateRange(min: Int, max: Int): Boolean = true
    fun validateRange(min: Long, max: Long): Boolean = true
    fun validateRange(min: Double, max: Double): Boolean = true
    fun validateRange(min: String, max: String): Boolean = true

    fun validateRangeInput(min: Int, max: Int, input: Int): Boolean = true
    fun validateRangeInput(min: Long, max: Long, input: Long): Boolean = true
    fun validateRangeInput(min: Double, max: Double, input: Double): Boolean = true
    fun validateRangeInput(min: String, max: String, input: String): Boolean = true
}