So I was annoyed by having to choose between mutab...
# announcements
n
So I was annoyed by having to choose between mutable dataclasses (which means they can mutate when passed as an input to a function), and immutable (all fields declared val), which means you have to mention the variable twice each time to do a replace operation. So came up with the following:
Copy code
interface MyInterface {
}

data class User(val x: String, val y: Int) : MyInterface

inline operator fun <T : MyInterface> T.plus(f: T.() -> T) : T {
    return this.f()
}

fun main() {
    var n = User(x = "hello", y = 5)
    n += { copy(y = 7) }
}
I think this is a nice approach in general for any user written immutable class. Don't have to use += either, could use e.g. %= which isn't as likely to have another use.
c
I fail to see how
n += { copy(y = 7) }
is better than
n = n.copy(y = 7)
. For me it just obscures the logic.
6
n
There's nothing wrong with that position, but I'm willing to bet you wouldn't say that about the built in map
Why do you like
myMap += "name" to 5
it's after all obscuring the fact that what's really happening is
myMap = myMap.put("name", 5)
I think both are less explicit, but both save you repeating the variable name twice, which can be quite annoying for longer variable names
If I change the variable to a (more) realistic name:
Copy code
employeeOfTheMonth = employeeOfTheMonth.copy(y=7)
Copy code
employeeOfTheMonth += { copy(y=7) }
I think the second's quite a bit more readable. And it potentially avoids bugs if you have two variables of the same type.
Copy code
// Intentional or not, hard to say
employeeOfTheMonth = employeeOfTheWeek.copy(y=7)
g
Could you show an example where you really need such code (with reassign)
n
Err anytime you want to replace some of the fields?
Your question is a bit confusing to me because the very fact that += works this way shows that you really need such code. Does it not?
Otherwise why bother to make it more convenient to add entries to Map List etc
k
For map and list
+
makes sense, you're literally adding something to the collection. With your data class you're not adding anything, you're just reassigning and that's
=
, not
+=
.
n
@karelpeeters + also modifies existing entries
k
Sure, but that's not really relevant.
n
You just said "you're literally adding...." vs reassigning
Just to be clear, I'm not saying that
+
specifically is great
It's obviously not
k
Yes you're adding most of the time and then when the key already exists you're replacing as a semi-edge case.
n
All I'm saying is that with
Map
List
etc it's convenient that there's a way to "modify" the object essentially, without listing it twice
You would agree, that's why
+=
works the way it does, right?
k
The situation is different, usually you use
+=
on mutable collections and then it actually mutates the object. "Listing it twice" is only for immutable objects. It's unfortunate that both concepts are mixed together because of operator overloading though.
For example, I doubt that there's a lot of usage of
+=
on immutable collections stored in vars.
n
So, I have no problem with your viewpoint per se
but what you are saying is that
+=
shouldn't do what it does in terms of falling back to
x = x + y
You're saying "it's unfortunate"
k
I think it should for consistency,
BigInteger
etc would be annoying to use otherwise, but I also think it's unfortunate it also applies to list and maps.
n
I think that's a fair view point. However, obviously implementing +/+= was purely optional for kotlin
for Map/list I mean
So I guess what I mean is, the language doesn't take this view, AFAICS
so if the language likes having += for Map, it should like having a solution for dataclasses
k
Well I like
+=
for mutable,
+
for immutable, and I like consistency so it's fine as-is for me.
But data classes are something different altogether, I don't get the connection.
n
Not sure what you mean by different exactly. A dataclass with all members declared
val
using
copy
to "modify" itself, is very very similar to a
Map
using
+
to insert items
k
For a map adding an entry is the "normal" operation and sometimes you also replace, for a data class adding doesn't make sense at all.
n
They're both read-only objects (or views of an object) that are emulating modification by creating a new object
Err forget about the fact that it's
+
Imagine you could do
x copy=(y=7)
or something like that
The point is not whether it's
+
or whether it's "adding". The point is to have syntactic sugar that lets you take a non-modifiable
var
, and rebind it easily
k
So to get away from add, you want something like
Copy code
var user = User(2, 3)
user=.x = 5
which compiles to
Copy code
user = user.copy(x = 5)
n
Sure. I don't really care about the exact syntax.
As long as I don't have to repeat the variable name really.
As a good example that's neither container nor Integer, consider e.g. a string
I think it makes strings a lot more ergonomic to do
x += "suffix"
vs
x = x + "suffix"
k
For those cases I tend to just use
buildString
.
n
Fair enough. It doesn't bug me that people prefer being explicit. It just bugs me people that think that
x += y
for read-only makes perfect sense for string/map/list but when you want something similar for an immutable data class, you're told that doesn't make sense, or its unclear, or what's the use case
k
+
really doesn't make sense though.
n
If you mean because it's the + symbol specifically, sure
Thinking about it some more I would probably use
%=
. It's the "mod" operator, mod being short for modification 😉
😲 1
Meh it looks weird once, you go to definition, see 5 lines of code, see exactly what it's doing.
Even looking at the use site, there's very little else that
x  %= { copy(x=7) }
could do
But basically, this is very similar to how operators get abused in e.g. for C++ for a half-decent reason: to get infix notation.
Kotlin gives you infix functions, so you don't need to use a random operator just to get infix.
Similarly, if Kotlin allowed user-named
foo=
functions, this wouldn't be necessary.
You could do it so that
x foo=(y = 7)
is an operator call that translates to
x = x.foo(y=7)
for any foo.
In essence once you do that, you actually have much more readable alternatives for String and Map too
Instead of
x += "suffix"
you could do
x concat=("suffix")
etc
c
BIt late to the discussion but FYI, this approach to mutation is known as “lenses” in the functional world, might want to look into that if you’re curious to learn more.