In what way are Julia sets immutable?

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?

2 Likes

Immutable types can have mutable properties.

Here is the definition of Set:

struct Set{T} <: AbstractSet{T}
    dict::Dict{T,Nothing}

    Set{T}() where {T} = new(Dict{T,Nothing}())
    Set{T}(s::Set{T}) where {T} = new(Dict{T,Nothing}(s.dict))
end
7 Likes

I guess this might be the case for many types which wrap a mutable type. e.g. specialized arrays like OffsetArrays.

1 Like

I know this from before, that you can modify mutable fields wrapped in an immutable struct, but this still caught me by surprise.

Maybe it’s the fact that the Dict inside the Set is so ‘hidden’, you don’t even know it’s there. So Set seems very much to be mutable. There’s some disconnect between the implementation detail that causes Set to be immutable, and the observed interface which is that Set is mutable.

So ismutable isn’t the right way to figure out the behaviour of Set. Could there be a Mutable trait or something along those lines, that tell you about the behaviour of a type, rather than its implementation?

6 Likes

I think this should work

function truly_immutable(T)
    T === String && return true
    T === Symbol && return true
    body = T isa UnionAll ? T.body : T
    ft = fieldtypes(body)
    body.mutable && return false
    isempty(ft) && return true
    return all(truly_immutable, ft)
end
1 Like

Indeed it isn’t. It’s for querying a property with a very specific meaning in Julia’s type system — its docstring refers the reader to mutable composite types.

Technically it is possible, but it would make sense to think a bit about the application first, with examples. Personally I would prefer to design my code in a way that I don’t have to think about this for objects I do not “own”, and for the rest I know anyway.

4 Likes