I like programming with objects that are immutable “all the way down” because they are just values. This function is a predicate to check whether I have one of these types.
Rich Hickey gives a nice talk about this, “The value of values”:
To check if an instance is recursively mutable (at least a start of such a function):
function recursive_mutable(x::T) where {T}
isstructtype(T) || return ismutable(x)
return any(recursive_mutable, get.(Ref(x), fieldnames(T)))
end
recursive_mutable(x::AbstractArray) = true
recursive_mutable(x::StaticArray) = any(recursive_mutable, x)
This will, for instance, catch SArray{Tuple{3}}(Any[1, 2, [1, 2]]), which wouldn’t be possible on type information alone (unless you want to be really harsh, and for instance say that Any is a tainted type. If you wish to implement something like that, you may be interested in ismutabletype(T) (which I think is more or less a replacement for T.mutable in recent versions)
I’m not seeing why that’s harsh? It seems like I’d want to call that object recursively mutable, since it has mutable components (the third element is a mutable array).
I guess I mean whatever’s the word for “harsh, but neutral”. “Stringent”?
Note, for instance, that if SArray{Any} is considered recursively mutable, then SArray{Tuple{3}}(Any[1, 2, 3]) would be too, even though that specific instance would be perfectly immutable.
How about then
function recursive_mutable(T::Type)
T === Any && return true
ismutabletype(T) && return true
isstructtype(T) && return any(recursive_mutable, fieldtypes(T))
return false
end
recursive_mutable(::Type{SArray{T, S}}) where {T, S} = recursive_mutable(S)
?
Unfortunately, Any and Union types are considered immutable, so you have to kludge like I’ve done with Any AFAICS