ilya.gorbunov

    ilya.gorbunov

    1 year ago
    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?
    Thomas

    Thomas

    1 year ago
    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.
    z

    Zhelenskiy

    1 year ago
    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.
    wasyl

    wasyl

    1 year ago
    I use
    buildList
    a lot. I like it mostly, although I hear some complaints that it’s less readable than
    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
    Dominaezzz

    Dominaezzz

    1 year ago
    I've used buildSet just once.
    n

    nkiesel

    1 year ago
    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(...) }
    louiscad

    louiscad

    1 year ago
    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.
    Zac Sweers

    Zac Sweers

    1 year ago
    I like them and wish JB would back the default listOf(), mapOf(), etc by them
    True immutability is a nice win
    dave08

    dave08

    1 year ago
    We also use them quite a bit, and migrated some older code to them which helped make it look much cleaner!
    m

    Marc Knaup

    1 year ago
    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.
    Fleshgrinder

    Fleshgrinder

    1 year ago
    @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()) }
    wasyl

    wasyl

    1 year ago
    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

    1 year ago
    @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 Arrays. • You can do the following:
    val l = buildList {
       val myList = this
       myList.add(...)
    }
    Fleshgrinder

    Fleshgrinder

    1 year ago
    @wasyl correct, there's a minimum size and small lists won't benefit.
    Javier

    Javier

    1 year ago
    @ilya.gorbunov will they be experimental in 1.5 too?
    ilya.gorbunov

    ilya.gorbunov

    1 year ago
    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.
    Javier

    Javier

    1 year ago
    renaming them is not going to happen no?
    ilya.gorbunov

    ilya.gorbunov

    1 year ago
    No plans for that.
    louiscad

    louiscad

    1 year ago
    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:
    buildList {
        if (whatever) add(
            Stuff(
                ...
            )
         )
        ...
    }
    with unary plus, it becomes the following:
    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?
    Fleshgrinder

    Fleshgrinder

    1 year ago
    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
    Dominaezzz

    Dominaezzz

    1 year ago
    Debatable
    louiscad

    louiscad

    1 year ago
    I'd say… depends on the developer and their mental condition at the time of writing the code 😄
    Fleshgrinder

    Fleshgrinder

    1 year ago
    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:
    fun String.doSomething() = buildString {
        forEach { /* or something similar that is available on both String and StringBuilder */ }
    }
    Solution:
    fun String.doSomething(): String {
        val it = this
        return buildString {
            it.forEach { /*...*/ }
        }
    }
    I stand by what I said above, this would avoid these issues altogether:
    fun String.doSomething() = String { it ->
        forEach { c -> it.append(c).append(c) }
    }
    z

    Zhelenskiy

    1 year ago
    @Fleshgrinder do you think it is better to have some way to explicitly name receivers in lampldas?
    Fleshgrinder

    Fleshgrinder

    1 year ago
    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.
    fun <E> Iterable<E>.doSomething() = buildList { forEach { /* ouch */ } }