Is there any good reason for the lack of `mapOfNot...
# stdlib
s
Is there any good reason for the lack of
mapOfNotNull
in stdlib to build a map from a list of nullable pairs from which only non-null pairs will be taken? There are two other functions (
listOfNotNull
and
setOfNotNull
) here and it seems quite reasonable to have a similar function for maps for the sake of consistency. I know that you can just write your own implementation, but it’s unlikely that you will do it most optimal and correct way (just for example, you should probably not forget about inlined
mapOfNotNull() = emptyMap()
, and etc).
👍 1
r
I'd also quite like a
fun <K, V : Any> Map<K, V?>.filterNotNullValues(): Map<K, V>
- I seem to end up writing that quite a lot.
m
Or more general, a filterValuesInstance and filterKeyInstance
but I believe theres talks about fixing all that with contracts
j
Technically, those functions have a
vararg
argument to act as builders for hardcoded lists of things. When you use
listOf(item1, item2)
you provide those variables by hand. Those variables may be nullable, so a
listOfNotNull(item1, item2)
kinda makes sense. However, for maps, you usually don't provide pair variables on the vararg but rather keys and values, and build non-nullable pairs out them on the spot with
to
, so I see little benefits of have such
mapOfNotNull()
function. In cases where you have an actual list of pairs already, you would instead transform the list like
filterNotNull().toMap()
s
yeah, we have plenty of options but it is not as convenient as simple function (besides they creates unnecessary intermediate collections). here is simple example:
Copy code
fun createByBuildMap(requestId: String?) = buildMap {
    this["appName"] = "foo"
    if (requestId != null) {
        this["requestId"] = requestId
    }
}

fun createByListOfNotNull(requestId: String?) = listOfNotNull(
    "appName" to "foo",
    requestId?.let { "requestId" to it },
).toMap()

// naive implementation only for example
fun <K, V> mapOfNotNull(vararg pairs: Pair<K, V>?) = pairs.filterNotNull().toMap()

fun createByMapOfNotNull(requestId: String?) = mapOfNotNull(
    "appName" to "foo",
    requestId?.let { "requestId" to it },
)
j
Honestly, the
buildMap
variant looks at least as clear as the
mapOfNotNull
variant to me. That's technically just personal preference, and I would probably use
put
over
this[...] = ...
, but the case for a
mapOfNotNull
doesn't seem that strong with this use case (if the goal is just to replace an
if
with a
let
).
As a side note, is it intentional that you don't want the
requestId
key to exist in the map when its value is not provided? For your use case it would seem just more obvious/simpler to use a
Map<String, String?>
and just have
mapOf("appName" to "foo", "requestId" to requestId)
. At least, this holds for key-based access of the map (but that's what maps are for). If your goal is to iterate the map's pairs, then probably a list would be more suited in the first place.
s
yep, i would like to not put the key to the map, because in the end it will be serialized to json. the example i have given is quite simplified, in real case the code is much noisy since there are a ton of pairs that brought to repetitive boilerplate
this["foo"] = "bar"
(or
put("foo", "bar")
and a lot of if-conditions
j
If the real use case is even more noisy and has more conditions, then that's even more reason (IMHO) to go with the builder approach. You can spread things out with blank lines for clarity, potentially use a single
if
for multiple entries, etc. rather than trying to cram everything in one vararg function call. But if that's a serialization use case, why not use a data class for this instead? And use the serialization library's feature to include or not optional keys?
s
It seems to me that we are now talking more about personal preferences 🙂 That is, in general, the language allows us to solve the same problem in different ways. The developer will choose which one is preferable. But I wanted to rely more on the fact that it is not consistent with the presence of the other two functions. Lets imagine there is no
listOfNotNull
and someone wants it in stdlib. Here we can give him the same advices (“just use `listOf(…).filterNotNull()`“). I think that would be weird 🙂 There are many examples of using constant lists, and exactly the same number of use-cases with using constant maps. These things are very similar.
My point is we have
listOf/setOf/mapOf
triplet but we have
listOfNotNull/setOfNotNull
duplet and that’s strange
There is no rhyme here that the poet wants 😛
j
Actually my first and main point is precisely that those are different things. And it's important to have a decent use case to decide whether to include a new stdlib function. A map is a mapping between keys and values, it is strange to talk about nullable pairs, because pairs are just a tool to create a map in one specific use case. The use case for creating a map with nullable pairs is unclear, and my arguments about style are trying to point out why you most likely don't really need a special function dealing with nullable pairs (that's just a consequence of wanting to use a style with
?.let {}
). On the other hand, the elements in a list are the main entities of the list, not just a construction tool, so wanting to filter out nullable values at construction is not really far fetched. And also I would argue that with the (newer) builder DSLs we don't even need those anymore either. Secondly, it's quite rare to deal with lists of nullable element types. We usually don't want nulls in lists or sets, so we'd rather not put the element there in the first place. On the other hand, a map is often accessed by key, and the absence of key yields a null value. A
get
call in a map returns a nullable type even if the map has a non-nullable value type. When used this way, it doesn't make a difference whether the key is present or not. So the use case of wanting to conditionally insert a key depending on whether the value is present is not really justified so far. Serialization may be an argument, but it doesn't look like a map is the correct representation in this case.
s
Well, I see now. This is definitely the answer. I would say that I cannot fully agree with this, but it is rational reasoning and a clear position by a slightly different angle.
👍 1
j
That's only my guess as to why tis function doesn't exist of course, I'm not part of the Kotlin team 😆