https://kotlinlang.org logo
Title
r

Rob Elliot

01/31/2023, 2:00 PM
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:
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

Sam

01/31/2023, 2:11 PM
Closest I can think of off the top of my head is this:
class User(map: MutableMap<String, String>): Map<String, Any> by map {
    var name: String by map
        private set

    init {
        name = "Joe"
    }
}
r

Rob Elliot

01/31/2023, 2:12 PM
Thanks, yes, that was where I was heading... slightly frustratingly noisy!
s

Sam

01/31/2023, 2:14 PM
wait, a half-remembered thought is coming back to me…
Okay, so, uh…
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
Ever feel like you’ve gone too deep and need to take a break from Kotlin? 😂
r

Rob Elliot

01/31/2023, 2:27 PM
Awesome, thanks!
You can even do this:
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

Ivan Pavlov

01/31/2023, 4:50 PM
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

Sam

01/31/2023, 4:58 PM
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

Ivan Pavlov

01/31/2023, 5:04 PM
Thanks, now I see
r

Rob Elliot

02/03/2023, 9:21 AM
@Sam mind if I write this up in a blog post?
s

Sam

02/03/2023, 9:28 AM
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

Rob Elliot

02/03/2023, 11:18 AM
s

Sam

02/03/2023, 11:23 AM
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 😄
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

Rob Elliot

02/03/2023, 11:38 AM
Yup, that's nicer! Done.