Check recursive immutability

struct Foo
    val::Vector{Int}
end

How can I check if a type is recursively immutable? I tried

function recursively_immutable(type) 
   !type.mutable && all(recursively_immutable(ft) for ft in fieldtypes(type))
end

but it doesn’t work on vectors, because vectors don’t have any fieldtypes.

There may be an existing solution, but while you are rolling your own you can add methods

recursive_mutable(::Type{AbstractArray}) = true
recursive_mutable(::Type{StaticArray}) = false
...

I think StaticArrays can have mutable interiors.

sa = SVector{3}([[],[2],[3]])
push!(sa[1], 1)

Same with FunctionalCollections.PersistentVector.

recursive_mutable(::Type{StaticArray{T}}) where {T} = recursive_mutable(T)

Nore that you’ll need to change your base case to work with arbitrary data types. And that this will only work with single type arrays,

StaticArray{Any}([
1, "hi", [3]
])

cannot be investigated statically, but would have to be checked by instance.

What is the use case for this?

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

Maybe “strict” is what you’re going for?

That code looks good. I guess I could return missing for things like Any and let the caller decide what to do.