How to use withDefault wrappers?

I've been looking for a way to return default value from a MutableMap, but also store it at the same time for future retrieval. The .withDefault only returns the default value, but doesn't store it. Calling .getOrPut every time I need to retrieve a value doesn't look like a good idea. I've came up with something like this:

val myMap = with(mutableMapOf<String, Set<String>>()) {
    withDefault { key -> getOrPut(key, { mutableSetOf<String>() }) }
}

This calls getOrPut within withDefault wrapper on the backing MutableMap object, which puts the missing key-value pair into the map and returns it.

To encapsulate the behaviour you can create a delegated property provider.

/** Wraps a [MutableMap]. Will generate a [defaultValue] if none exists, and set it into the map. */
fun <K, V> mapWithPutDefault(defaultValue: (key: K) -> V): ReadWriteProperty<Any?, MutableMap<K, V>> =
  object : ReadWriteProperty<Any?, MutableMap<K, V>> {

    private var map: MutableMap<K, V> = with(mutableMapOf<K, V>()) {
      withDefault { key -> getOrPut(key) { defaultValue(key) } }
    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): MutableMap<K, V> = map

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableMap<K, V>) {
      this.map = value
    }
  }

fun main() {
    val myMap: MutableMap<String, String> by mapWithPutDefault { "some default value for $it" }
    println("map is empty: $myMap")
    // map is empty: {}
    val someValue = myMap.getValue("my-key")
    println("map returns a default value: $someValue")
    // map returns a default value: some default value for my-key
    println("map now has a value: $myMap")
    // map now has a value: {my-key=some default value for my-key}
}

Looks like in Kotlin 1.1 this actually works if you use the getValue() function instead of the get() function.


As of Kotlin 1.0 a wrapper returned by withDefault is only usable in property delegation use cases.

val map = mutableMapOf<String, Set<String>>().withDefault { mutableSetOf() }

var property: Set<String> by map // returns empty set by default

Tags:

Kotlin