Is there a way to delegate to a map and initialise...
# getting-started
r
Is there a way to delegate to a map and initialise it at the same time? Preferably immutably... I'm looking at https://kotlinlang.org/docs/delegated-properties.html#storing-properties-in-a-map , but I want to initialise inline... This (pseudo code, does not compile...) is what I want to do:
Copy code
class User(map: MutableMap<String, Any> = mutableMapOf()): Map<String, Any> by map {
    val name: String = "Joe" by map
    val age: Int     = 3 by map
}

val user = User()

user.toMap() == mapOf("name" to "Joe", "age" to 3)
user.name == "Joe"
user.age == 3
Ideally I don't want to repeat the
name
or
age
key / property names.
s
Closest I can think of off the top of my head is this:
Copy code
class User(map: MutableMap<String, String>): Map<String, Any> by map {
    var name: String by map
        private set

    init {
        name = "Joe"
    }
}
r
Thanks, yes, that was where I was heading... slightly frustratingly noisy!
s
wait, a half-remembered thought is coming back to me…
Okay, so, uh…
Copy code
class User(map: MutableMap<String, Any>) : Map<String, Any> by map {
    val name: String by map withInitialValue "Joe"
}

infix fun <V> MutableMap<String, V>.withInitialValue(initialValue: V) =
    PropertyDelegateProvider<Any, Map<String, V>> { _, property ->
        apply { put(property.name, initialValue) }
    }
edit: omg it can be an infix function too
🎉 2
Ever feel like you’ve gone too deep and need to take a break from Kotlin? 😂
r
Awesome, thanks!
You can even do this:
Copy code
class User(map: MutableMap<String, Any>) : Map<String, Any> by map {
    val name: String by map("Joe")
}

operator fun <V> MutableMap<String, V>.invoke(initialValue: V) =
    PropertyDelegateProvider<Any, Map<String, V>> { _, property ->
        apply { put(property.name, initialValue) }
    }
i
If I'm not mistaken, this https://kotlinlang.slack.com/archives/C0B8MA7FA/p1675174286818479?thread_ts=1675173615.409839&amp;cid=C0B8MA7FA solution initializes values in map when instance is created and other options put value only on the first access to property, so they are different
s
I believe that the
PropertyDelegateProvider
is created when the property is declared. So that solution would put the value in the map on instance creation too. I checked by adding some
println
calls and it does seem to work that way.
(It’s different from a normal property delegate, which indeed would only be invoked on property access)
i
Thanks, now I see
r
@Sam mind if I write this up in a blog post?
s
Go for it! Especially if you feel like giving me a shout-out 😉 I’m just getting started with my own blog (https://medium.com/@sam-cooper)
Drop a link here if you do, I would love to read it
r
s
Nice! It’s really cool to see the use case for it. I like how you packaged it up into an abstract class too.
actually that gives me another idea 😄
Copy code
abstract class AbstractPropertyMap<V>(
    private val properties: MutableMap<String, V> = mutableMapOf()
) : Map<String, V> by properties {
    protected fun mapProperty(initialValue: V) =
        PropertyDelegateProvider<Any, Map<String, V>> { _, property ->
            properties.apply { put(property.name, initialValue) }
        }
}

class Links(
    id: String,
    otherId: String,
) : AbstractPropertyMap<URI>() {
    val self by mapProperty(URI.create("/v1/$id"))
    val other by mapProperty(URI.create("/v1/other/$otherId"))
}
Also, can you make it link to my medium page instead of this Slack? 🙏 I know, shameless self-promotion 😄
I’ll link back if I do a post about property delegates; the way you’ve used it is really cool
r
Yup, that's nicer! Done.
🙇 1
🐕 1