Is it possible to have an infix function with a re...
# getting-started
a
Is it possible to have an infix function with a reified type parameter take in a type? i.e
Copy code
inline infix fun <reified T : DataObject> Player.get(type: Class<T>): T {
    return ComponentInit.PLAYER_DATA.get(this).get<T>(type)
}
Looking to ideally replace the class parameter with a type if thats somehow possible?
Copy code
val testData = player get TestDataObject::class.java
feels a little obtuse,
Copy code
val testData = player get TestDataObject
would be much nicer
s
with reified generics you could skip
type
parameter altogether and use class reference on `T`:
Copy code
inline fun <reified T : DataObject> Player.get(): T {
    return ComponentInit.PLAYER_DATA.get(this).get<T>(T::class.java)
}

val testData = player.get<TestDataObject>()
a
Ah i already have something like that, i was wanting to mess with infix to see if i could get it to do what i wanted, make it easier to read later on
d
It's not posssible. infix requires two values (one before, and one after), but the type is not a value.
However, you could probably do something like this:
Copy code
inline infix fun <reified T : DataObject> Player.get(type: Class<T>): T = TODO()
inline fun <reified T : DataObject> of() = T::class.java

fun main() {
    val player = Player()
    val item = player get of<DataObject>()
}
s
just as a fun fact: it is possible to achieve
val testData = player get TestDataObject
if you would use custom companion objects implementing some generic construct:
Copy code
interface DataObjectKey<T : DataObject>

class TestDataObject : DataObject {
    companion object Key : DataObjectKey<TestDataObject>
}

inline infix fun <reified T : DataObject> Player.get(key: DataObjectKey<T>): T {
    return ComponentInit.PLAYER_DATA.get(this).get<T>(T::class.java)
}

val testData = player get TestDataObject // TestDataObject is a companion object reference here. T is inferred from companion object's generics
This technique of using custom companion objects can be seen for example in
kotlinx-coroutines
, in
CoroutineContext.Element
, it allows you to do stuff like
coroutineContext[Job]
- Job is also a companion object reference there. But that's just to show you the possibilities. In your example
player.get<TestDataObject>()
would be the most sensible choice. The approach is readable and consistent with what's already in kotlin stdlib and most popular 1st and 3rd party libraries
nod 1
d
That's a good suggestion SJ. Amo, if you go so far as using the companion object key (which isn't a bad idea, really), i suggest using a regular
operator fun <T:DataObject> get(key: DataObjectKey<T>)
function rather than an infix. This would provide the more standard notation of
player[TestDataObject]
a
Oh interesting i completely forgot about operator functions, good catch!
s
that's right, non-infix operator fun here would definitely make more sense here! I guess this approach can be sensible in come cases, but here and in majority of the other cases I would consider it and overkill and stick with reified generics. Here are some reasons: • You can call class reference on every class, with custom companion objects you are limited to only classes which have those companion objects altered • with reified generics you write function once and that's all. Using companion objects you would have to write and maintain them for every single class of which reference you want to read. • reading
player[TestDataObject]
may be a little confusing at first, it's definitely non standard way of achieving class reference. Reading companion objects implement some custom construct also adds a little mental overhead. That being said, if you're not in any commercial environment and you're just working on some personal project trying to have some fun and learn at the same time, then go for whatever you find coolest 🙂
a
Oh hey having the operator fun also be infix allows for varying ways of use, i understand its atypical but i'm using this as a project to sorta get creative 🙂
val testData = player get TestDataObject
or
val testData = player[TestDataObject]
! sorta nice to have
d
Sometimes consistency is more important than choice 😉
Though if you're just exploring what can be done, go for it. I've made some crazy stuff in my day by doing just that.
a
This is sort of an internal library for some for-fun projects for a handful of devs, so i like giving options!
👍 1
d
Copy code
open class AttributeKey<T : Any>(protected val type: KClass<T>) {
    open fun from(map: Object): T? = type.safeCast(map.attributes[this])
    fun isIn(map: Object) = type.isInstance(map.attributes[this])
}

open class AttributeKeyWithDefault<T : Any>(
    kClass: KClass<T>,
    private val default: (Object) -> T,
    private val saveDefault: Boolean = true,
) : AttributeKey<T>(kClass) {

    override fun from(map: Object): T {
        return map.attributes[this]?.let(type::safeCast) ?: default(map).also {
            if (saveDefault) map.attributes[this] = it
        }
    }

    fun addTo(map: Object) {
        map.attributes.getOrPut(this) { default(map) }
    }
}

class Object {
    internal val attributes = mutableMapOf<AttributeKey<*>, Any?>()

    operator fun <T : Any> set(key: AttributeKey<T>, value: T) {
        attributes[key] = value
    }

    fun <T : Any> add(key: AttributeKeyWithDefault<T>) {
        key.addTo(this)
    }

    fun remove(key: AttributeKey<*>) {
        attributes.remove(key)
    }
    
    operator fun contains(key: AttributeKey<*>) = key.isIn(this)

    operator fun minusAssign(key: AttributeKey<*>) = remove(key)
    operator fun <T : Any> plusAssign(key: AttributeKeyWithDefault<T>) = add(key)

    operator fun <T : Any> get(key: AttributeKey<T>) = key.from(this)
    operator fun <T : Any> get(key: AttributeKeyWithDefault<T>) = key.from(this)
}
Somethign similar that I'm working on.
c
Additional note to what everything else said before: if you go the simple route:
Copy code
player.get<TestDataObject>()
then you can also call it as:
Copy code
val o: TestDataObject = player.get() // inferred

fun foo(o: TestDataObject) …

foo(player.get()) // inferred
nod 1
But since you're implementing a data-driven structure, I think @Szymon Jeziorski’s approach with custom companion objects that are used to reference the proper data is the best solution
As they mentioned, you can see this in CoroutineContext, but that's also how CompositionLocal and a lot of other stuff is implemented