Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use withDefault wrappers?

Tags:

kotlin

I want to use MutableMap with defaults:

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

but I can't use getOrImplicitDefault method because withDefault returns MutableMap<String, Set<String>> type. Moreover, I can't cast to MutableMapWithDefault interface because this is a private interface.

I can't use get method either because it returns a nullable type. It is ok because this is a method on the MutableMap interface (moreover it doesn't call defaultValue callback for take default value).

Seems like this functionality is not correctly implemented in Kotlin, or I am using it wrong. So, how do I use withDefault wrappers correctly?

like image 579
Maxim Avatar asked Apr 25 '16 09:04

Maxim


3 Answers

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
like image 148
Ilya Avatar answered Oct 23 '22 03:10

Ilya


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

like image 7
sschuberth Avatar answered Oct 23 '22 02:10

sschuberth


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}
}
like image 5
yaworski Avatar answered Oct 23 '22 03:10

yaworski