“Immutable objects are simple.“ - Brian Goetz
Author RelationalAI-oss
2 Stars
Updated Last
4 Years Ago
Started In
January 2020


Build Status

This package simply provides a small trait function, is_purely_immutable(x), which checks if a value is deeply, purely (i.e. recursively), logically immutable.

A purely immutable value can never change, and thus is safe to use in purely functional datastructures.

This is needed as a separate concept from Base.isimmutable(), since that refers only to whether a value is an instance of an immutable struct in julia, but this is neither necessary nor sufficient to determine whether a value can logically change from its current value.

As shown here, one shouldnt use isimmutable to detect whether a value can change:

julia> struct S x end

julia> s = S([]); s1 = deepcopy(s)

julia> isimmutable(s1), s1.x == s.x
(true, true)

julia> push!(s.x, 10)
1-element Array{Any,1}:

julia> s1.x == s.x

Instead, you can use is_purely_immutable to accurately check whether a value can ever change:

julia> struct S x end

julia> is_purely_immutable(S([]))  # false, since S.x can be mutated (as above)

julia> is_purely_immutable(S(1))  # true, since S(1) is recursively immutable

There are also some values implemented as mutable structs, but which can never be modified, such as Strings (for more: JuliaLang/julia#30210), and is_purely_immutable handles them correctly:

julia> is_purely_immutable("strings are logically immutable")

Extending for custom types

If you have a type whose values aren't handled correctly by the default implementations (e.g. an immutable struct that is actually somehow mutable (such as FixedSizeArrays), or a mutable struct whose accessors are all disabled), you should extend this function with a method for your type. For example:

IsPurelyImmutable.is_purely_immutable(::MyType) = true


is_purely_immutable(val) :: Bool

A trait function that returns true if a value is purely immutable, meaning its value can never change in any way from the value it currently holds, and thus is safe to use in purely functional datastructures. This requires a value to be immutable itself, and recursively purely immutable for all of its fields.

Users should override this function to set the trait for their own types.

NOTE: All methods of this function must be "pure functions", meaning they cannot depend on any outside state, and must always return the same result for a given value.

The default method returns true if:

  • The value is an unmodifiable literal (Int, String, etc), or
  • The value's type is a julia immutable type, that recursively only contains other immutable types, or
  • The value is an empty mutable type (has no fields).

Note that (like Base.isimmutable) this function works on values, not types. This may be counter-intuitive, but immutability is indeed a per-instance property: e.g. an immutable type with an abstract field may be constructed with a mutable or immutable value; or a user defined type may be able to "freze" and become immutable at runtime (e.g. Mutable-Until-Shared types); and finally because (like isimmutable) it is only meaningful for concrete types. See the Julia Docs section on Mutable Composite Types for more on the meaning of immutable in Julia.