https://kotlinlang.org logo
Title
k

karelpeeters

05/19/2018, 7:39 AM
Yup, the problem is that those fields are vars, so it's ambiguous whether to add to the existing collection or create a new one and put it in
x
.
b

benleggiero

05/20/2018, 6:48 AM
Okay… but I don’t care. Either way the next line will have the collection I want. Also, there’s no way to disambiguate it? Really?
k

karelpeeters

05/20/2018, 7:14 AM
Well the same collection might be used elsewhere, and maybe you can't just mutate it.
You can do
.allAll(...)
if you want to mutate or
y = y + x
to make a new one.
b

benleggiero

05/20/2018, 4:43 PM
Hopefully Valhalla will simplify this 😕
k

karelpeeters

05/20/2018, 5:20 PM
Huh? Isn't Valhalla about value types? This is just an unfortunate ambiguity.
b

benleggiero

05/20/2018, 5:43 PM
Swift takes advantage of value types to solve this ambiguity. Instead of having a separate
Set
vs
MutableSet
, Swift just has
Set
, and whether it's mutable is decided by how it's declared. Mutable sets can be fields declared with
var
, or function parameters declared with
inout
. When you mutate a Swift value type, its copy-on-write system actually creates a complete memcopy of the original value, with your mutations applied. If the original is no longer accessible (its variable name was mutated or reassigned), it's immediately deallocated, so there's no memory bloat from this behavior. So bringing it back to Kotlin, if there was just
Set
and no
MutableSet
, we could do this no problem:
var x = setOf<Foo>()
val y = setOf<Foo>(Foo.instance)

x += y // OK!
y += x // Impossible!
Here,
x
would be a mutable
Set
and
y
is an immutable
Set
. Because
x
is a mutable value with copy-on-write semantics, its contents is copied to a new location in memory, mutated such that the contents of
y
are appended to the end, and reassigned to the variable
x
. The old contents are deallocated/garbage-collected/whatever. On the next line, we have nonsense. Not only is
y
an immutable set, but its pointer is also immutable (traditional
val
behavior). So there is no
=
nor
+=
operator that would apply. No more ambiguity, much more expressive and intuitive. I really hope Valhalla allows for this 🙏🏼
k

karelpeeters

05/20/2018, 5:54 PM
Is there no distinction between instance and reference mutability in Swift?
a

araqnid

05/21/2018, 3:21 PM
"copy-on-write system" - this sounds like it's not a mutable map in the sense that Kotlin mutable maps are ("persistent" is a widespread but confusing term for them)
b

benleggiero

05/21/2018, 4:33 PM
@karelpeeters there is. `struct`s and `enum`s are passed by value, and the values themselves are never mutated, but instead copied as described above when required. This is similar to how JVM primitives are handled. `class`es are passed by reference, and act as one would expect. This is similar to how JVM classes are handled. This gives peace-of-mind that if you have an instance of a
struct
, its value won't change from another thread or by passing it to a function (unless you use the
inout
keyword)
@araqnid correct, in Swift, things that are passed by value aren't mutable in the common use of the term
k

karelpeeters

05/21/2018, 4:36 PM
Is it possible to have a variable that has an immutable reference to a mutable list? Or the other way around, a mutable reference to an immutable list?
b

benleggiero

05/21/2018, 4:58 PM
Yes, in Swift, if you really care about passing a struct by reference, you can wrap it in a class. Since Swift classes don't have to inherit from anything, this is very lightweight. Then when you pass the wrapper class's instance by reference, all its contents come with it by reference. Also, again, using
inout
on a function parameter allows the function to mutate the contents in a way very similar to how one usually passes things by reference, so there is rarely ever a reason to use the wrapper workaround
For example, in Swift:
let first = ["foo", "bar", "baz"]

var second = first // runtime takes note, but does not yet copy anything
print(second) // ["foo", "bar", "baz"]

var third = second // same situation
print (third) // ["foo", "bar", "baz"]

third [1] = "qux" // the contents of `first` are copied into `third` and then mutated
var fourth = third
print (first) // ["foo", "bar", "baz"]
print (second) // ["foo", "bar", "baz"]
print (third) // ["foo", "qux", "baz"]
print (fourth) // ["foo", "qux", "baz"]

second.remove(at: 2) // contents of `first` are copied and mutated
print (first) // ["foo", "bar", "baz"]
print (second) // ["foo", "bar"]
print (third) // ["foo", "qux", "baz"]
print (fourth) // ["foo", "qux", "baz"]

fourth.append("hoge")
print (first) // ["foo", "bar", "baz"]
print (second) // ["foo", "bar"]
print (third) // ["foo", "qux", "baz"]
print (fourth) // ["foo", "qux", "baz", "hoge"]

func mutate(_ array: inout [String]) {
    array.append("hello")
}

mutate(&second)
mutate(&fourth)

print (first) // ["foo", "bar", "baz"]
print (second) // ["foo", "bar", "hello"]
print (third) // ["foo", "qux", "baz"]
print (fourth) // ["foo", "qux", "baz", "hoge", "hello"]