https://kotlinlang.org logo
Title
s

Shawn A

04/24/2019, 3:02 PM
I have a question about type inference in the example below. Given this method:
fun <R : Any> fromAttr(
        prop: KProperty1<T, R>,
        transformer: (AttributeValue) -> R
    ) {
        transformers.put(constructorParams.getValue(prop.name), transformer)
    }
If I don't supply the type argument I am able to pass a
prop
with a different
R
than
transformer
. For instance:
u.fromAttr(Widget::count) { false }
Where
Widget::count
is a
Long
. I have to use the form
u.fromAttr<Long>(Widget::count) { false }
to get a compiler error. I assume this is happening because of type erasure so Kotlin is inferring
R
to be
Any
?
d

diesieben07

04/24/2019, 3:04 PM
It's not because of type erasure, it is because
(AttributeValue) -> R
can be thought of as
Function1<AttributeValue, out R>
. And that is assignable to
Function1<AttributeValue, out Any>
, so
Any
is inferred.
s

Shawn A

04/24/2019, 3:08 PM
Thanks. So, it seems unavoidable that I must use
u.fromAttr<...>
d

diesieben07

04/24/2019, 3:08 PM
Depends on your use case. What do you use this
transformers
map for?
s

Shawn A

04/24/2019, 3:10 PM
It maps AWS dynamoDB results to an object. Here is the full class:
class Unmarshaller<T: Any>(private val cls: KClass<T>) {
    private val transformers = mutableMapOf<KParameter, (AttributeValue) -> Any>()
    private val constructorParams = cls.primaryConstructor!!.parameters
        .map { it.name to it }
        .toMap()

    fun <R : Any> fromAttr(
        prop: KProperty1<T, R>,
        transformer: (AttributeValue) -> R
    ) {
        transformers.put(constructorParams.getValue(prop.name), transformer)
    }

    fun invoke(item: Map<String, AttributeValue>): T {
        val params = transformers
            .mapValues { (k, func) -> func(item.getValue(k.name!!)) }

        return cls.primaryConstructor!!.callBy(params)
    }
}
@Test
    fun `unmarshall an object from a map of name to AttributeValue`() {
        val item = mapOf(
            "name" to strAttr("foo"),
            "count" to longAttr(55)
        )

        val u = Unmarshaller(Widget::class)
        u.fromAttr(Widget::name) { it.s() }
        u.fromAttr(Widget::count) { it.n().toLong() }
        
        val widget = u.invoke(item)
    }
d

diesieben07

04/24/2019, 3:16 PM
What you could do is something like this:
class WrappedProperty<T, R>(val property: KProperty1<T, R>)

fun <R : Any> fromAttr(prop: WrappedProperty<T, R>, transformer: (AttributeValue) -> R) {

    }
Not as elegant, but unfortunately there is no way (that I know of) to "override" the declaration-site variance (
out
) of the
KProperty
here.
s

Shawn A

04/24/2019, 3:19 PM
hmm ya. I'll probably just stick with having to supply the type argument. Thanks though.
though, if you supply the type and return the correct type from the lambda intellij tells you its not needed
d

diesieben07

04/24/2019, 3:21 PM
Well, yes, because it is indeed not needed.
s

Shawn A

04/24/2019, 3:21 PM
haha right
I was just hoping to get more help from the compiler but that won't happen unless I adopt your wrapper approach.
d

diesieben07

04/24/2019, 3:23 PM
There is something the Stdlib has for its own usage for this problem:
@OnlyInputTypes
. But it's internal.