https://kotlinlang.org logo
#stdlib
Title
# stdlib
i

ilya.gorbunov

03/10/2021, 12:33 PM
Hi there! What do you think about the collection builder functions,
buildList
,
buildSet
and
buildMap
, introduced as experimental in 1.3.70? Do you use them in your code? What are your impression of them?
t

Thomas

03/10/2021, 12:45 PM
I use
buildList
a lot in my code now, it is really useful. The code looks much better with it. I don’t really use the other two build functions for Set and Map.
8
z

Zhelenskiy

03/10/2021, 12:57 PM
I use
buildList
and
buildMap
a lot while I almost don't use
buildSet
. Some
buildMutable...
functions may also be helpful but they can be done with specifying implementation:
ArrayList().apply { ... }
. However, there are both mutable and read-only analogues for
listOf
,
mapOf
, so it is better to have both ones here too.
w

wasyl

03/10/2021, 1:11 PM
I use
buildList
a lot. I like it mostly, although I hear some complaints that it’s less readable than
Copy code
listOf(firstElement)
  + secondElement
  + thirdElement
and it’s easy to have an expression inside but forget to wrap it in an
add(…)
call. With
+
from the example above it’d be much more difficult to unintentionally not add something
d

Dominaezzz

03/10/2021, 5:45 PM
I've used buildSet just once.
n

nkiesel

03/10/2021, 6:57 PM
Couple of points: • when we have
buildList
, we should also have the other primary constructs like sets and maps handled for consistency. Which brings up the question about `buildArray`: do we need that? • there is a sweet-spot for buildXXX where the block is complicated enough to e.g. require some variables or statements but not so complicated that we loose track of what an
add
or
put
call operates on. I sometimes wished I could use
val l = buildList { mylist -> ...; myList.add(...) }
l

louiscad

03/10/2021, 10:48 PM
It's a little annoying that you need to write
this
to use the
+=
operator, or use
add
, but I still like
buildList
and used it in a few places, though the experimental status didn't encourage me to use it more.
z

Zac Sweers

03/11/2021, 1:03 AM
I like them and wish JB would back the default listOf(), mapOf(), etc by them
True immutability is a nice win
d

dave08

03/11/2021, 2:57 AM
We also use them quite a bit, and migrated some older code to them which helped make it look much cleaner!
m

Marc Knaup

03/11/2021, 6:05 PM
I’ve barely used or felt the need for the new
build*
functions. • I rarely have the need. Most lists etc. are created by function chains anyway. • In cases when I’ve tried the builders they made code more difficult to read rather than easier. Partially because thee are
add()
calls scattered around and you don’t immediately see the receiver. • In many cases it feels wrong to introduce a scope and indent code just to create a list. The list is often a side-effect or (partial) result of another primary intent. Indenting it focuses the code on creating a list, not of the primary intent. For
buildString
it’s different somehow. Maybe because it’s way more common? And
append
is clearer in such contexts? And creating the String is usually the primary intent of the code within the block. I’ve run a quick search over my entire Kotlin codebase. There isn’t a single instance left where I use it. I guess there are good cases for these builders. I just haven’t found some in my back-end & front-end code yet.
f

Fleshgrinder

03/11/2021, 10:29 PM
@Zhelenskiy I added mutable versions in the original PR but they were rejected, don't remember the reasoning. I'd like to use them more often but the experimental status which is a result of the experimental type inference for builders is a blocker. It also fails with
buildMap
very often.
@wasyl the main advantage of the
build*
functions over what you have is that they take the expected size and allow you to build collections in the most efficient way without resizing. They are not meant to contain complex logic (but obviously can). A situation where I like to use them is in conjunction with `vararg`:
fun f(s: String, vararg ss: String) = buildList(1 + ss.lenght) { add(s); addAll(ss.toIterable()) }
w

wasyl

03/12/2021, 8:19 AM
Yes, I’m aware 🙂 My comment was from usability standpoint, and with small lists I don’t think there’s huge difference in peformance
z

Zhelenskiy

03/12/2021, 8:36 AM
@nkiesel • Is
buildArray
different from just
Array
constructor? Don't think so. You have to specify size and values per index. You have no
add
method for `Array`s. • You can do the following:
Copy code
val l = buildList {
   val myList = this
   myList.add(...)
}
f

Fleshgrinder

03/13/2021, 9:14 PM
@wasyl correct, there's a minimum size and small lists won't benefit.
j

Javier

03/24/2021, 2:21 PM
@ilya.gorbunov will they be experimental in 1.5 too?
i

ilya.gorbunov

03/25/2021, 2:12 PM
Yes, collection builders will have to remain experimental in 1.5 because they rely on the experimental builder type inference. We plan some changes in the latter in order to stabilize it and they will likely affect the usages of collection builders as well.
j

Javier

03/25/2021, 2:17 PM
renaming them is not going to happen no?
i

ilya.gorbunov

03/25/2021, 2:22 PM
No plans for that.
😌 1
👍 2
l

louiscad

03/26/2021, 1:46 PM
I'd like to use
buildList
in a Gradle plugin and possibly libraries, but I'm not sure if it's safe when it comes to forward compatibility since the RequiresOptIn annotation only says it's experimental overall.
I think I'd like to have the unary plus operator in the scope of the
buildList
lambda, because of that annoying indent:
Copy code
buildList {
    if (whatever) add(
        Stuff(
            ...
        )
     )
    ...
}
with unary plus, it becomes the following:
Copy code
buildList {
    if (whatever) +Stuff(
        ...
     )
    ...
}
I guess that's what the unary plus operator has been designed for, and there's a precedent in kotlinx.html. Is that something that was ever considered for
buildList
and friends @ilya.gorbunov?
f

Fleshgrinder

03/31/2021, 7:54 AM
I generally think that it was a bad call to make the receiver
this
in all of the
build
functions as it is a source of many bugs. Having the receiver as first argument would automatically solve your issue as well since you'd have
it + Stuff
d

Dominaezzz

03/31/2021, 8:01 AM
Debatable
l

louiscad

03/31/2021, 8:02 AM
I'd say… depends on the developer and their mental condition at the time of writing the code 😄
f

Fleshgrinder

04/24/2021, 5:04 PM
Luckily I developed a keen eye for this, just had such a case again. It always happens in extension functions and follows a similar pattern:
Copy code
fun String.doSomething() = buildString {
    forEach { /* or something similar that is available on both String and StringBuilder */ }
}
Solution:
Copy code
fun String.doSomething(): String {
    val it = this
    return buildString {
        it.forEach { /*...*/ }
    }
}
I stand by what I said above, this would avoid these issues altogether:
Copy code
fun String.doSomething() = String { it ->
    forEach { c -> it.append(c).append(c) }
}
z

Zhelenskiy

04/24/2021, 5:17 PM
@Fleshgrinder do you think it is better to have some way to explicitly name receivers in lampldas?
f

Fleshgrinder

04/24/2021, 5:19 PM
I don't think anything would help here, other than not having
StringBuilder
as receiver of
buildString
. The problem is that the sources often have the same functions, and this confuses people. I already see the same happening with
buildList
and friends.
Copy code
fun <E> Iterable<E>.doSomething() = buildList { forEach { /* ouch */ } }