4

If I create a set in Julia, then Julia will tell me that the set is immutable.

julia> pets = Set(["dog", "cat", "budgerigar"])
Set{String} with 3 elements:
  "cat"
  "budgerigar"
  "dog"

julia> ismutable(pets)
false

Nonetheless, I can modify the set in place.

julia> push!(pets, "orangutan")
Set{String} with 4 elements:
  "orangutan"
  "cat"
  "budgerigar"
  "dog"

And I can check that the set contents have changed.

julia> display(pets)
Set{String} with 4 elements:
  "orangutan"
  "cat"
  "budgerigar"
  "dog"

Similarly, I can delete from the set in place

julia> delete!(pets, "dog")
Set{String} with 3 elements:
  "orangutan"
  "cat"
  "budgerigar"

So my question is, in what way are sets immutable? In what way is their mutability different when compared with dictionaries?

julia> ismutable(Dict())
true

What am I not understanding?

Mark Graph
  • 4,969
  • 6
  • 25
  • 37
  • 1
    I could be wrong (hence this is a comment not an answer) but I think a `Set` is just a wrapper on a `Dict`, see e.g. `fieldnames(Set)`. So a `Set` is immutable in the sense that once you create a `Set` `s`, you can't do the operation `s.dict = Dict("a"=>"new dict")`, i.e. you can't create a new `Dict` at `s.dict`. However, a `Dict` is mutable, so you can modify the existing `Dict` at `s.dict`, which is exactly what the `push!` and `delete!` methods do. EDIT: just checked the source and this is indeed how it works so I'll convert it to an anwers – Colin T Bowers Oct 26 '20 at 00:00

1 Answers1

3

If you check the source for Set, you can see that a Set is just a wrapper on a Dict{T,Nothing}, and when you add a new item, say x::T, to the Set, julia just creates a new entry in the Dict of x => nothing. That is, the items are stored in the keys of the Dict, and the values are not relevant so are set to nothing.

Clearly, a Dict needs to be mutable, as you observed in the question. The Set itself does not need to be mutable, since all the mutation is performed within the Dict that is wrapped by the Set. To see what I mean, we can mess around with the internals of a Set.

julia> s = Set(["a", "b"])
Set{String} with 2 elements:
  "b"
  "a"

julia> s.dict
Dict{String,Nothing} with 2 entries:
  "b" => nothing
  "a" => nothing

julia> push!(s, "c")
Set{String} with 3 elements:
  "c"
  "b"
  "a"

julia> s.dict["d"] = nothing

julia> s.dict
Dict{String,Nothing} with 4 entries:
  "c" => nothing
  "b" => nothing
  "a" => nothing
  "d" => nothing

julia> s.dict = Dict("a new set"=>nothing)
ERROR: setfield! immutable struct of type Set cannot be changed
Stacktrace:
 [1] setproperty!(::Set{String}, ::Symbol, ::Dict{String,Nothing}) at ./Base.jl:34
 [2] top-level scope at REPL[14]:1

The key insight here is that I can play around with the mutability of s.dict as much as I want. But because Set is immutable, I can't replace s.dict with an entirely new Dict. That triggers the error you see in my session above.

If it isn't clear what it means for an immutable type to have mutable internals, I asked a similar question about this years ago on StackOverflow. It can be found here

Colin T Bowers
  • 18,106
  • 8
  • 61
  • 89
  • 2
    `ismutable` isn't really very useful. It doesn't tell you whether a value is semantically mutable, just whether it is implemented as an immutable type. In other words, it tells you about implementation details, not about actual usage. Sets are mutable, but their type isn't. Similarly, strings are immutable, but the type `String` is mutable. This is quite confusing, and I wonder whether `ismutable` should even exist, since there is also an `ismutabletype` function. – DNF Aug 15 '22 at 14:10
  • @DNF Totally agreed. I've never found a use for it personally and it isn't hard to construct examples that feel very un-intuitive. – Colin T Bowers Aug 17 '22 at 11:54