I created a small package to handle lazily initialized fields. A lazily initialized field is a field in a struct that does not get initialized on the creation of the struct but at some later point. While it is a good idea, in general, to fully initialize structs, there are times where for e.g. performance reason you want to only initialize certain fields when they are actually needed. I recently had such a situation and I wasn’t 100% satisfied with the solutions Julia itself provided, so I made this package.
Installation, examples, and documentation can be found at LazilyInitializedFields.jl (thanks PkgPkage.jl). It goes through how you use the package and contrast it with some of the other common ways of dealing with lazy fields in Julia (using a Ref or using a Union field) and why these didn’t fulfill the requirements I had. The page also shows how the package is implemented which some people might find interesting.
Note that the package is still in the registration process so has to be added by URL (as shown in the docs).
Will this impact performance relative to using immutable structs?
Alright, that makes sense. On another note though, mutating is then disabled for lazily initialized mutable structs as well (and that is true for all fields, not just the lazily initialized ones), which effectively makes them immutable with standard usage.
Not sure if that’s feasible but it might be desirable to allow standard mutation for lazily initialized mutable structs.
I think it might be nice to provide a way to make the struct immutable and then users can use things like https://github.com/jw3126/Setfield.jl to update values without mutation if they desire it.
I personally don’t think that would be too useful because it is likely the object will be stored somewhere (maybe in multiple places) and it is likely you do in fact want to change the object itself, not just get a new, initialized object.
I personally think that my desire to partially initialize a struct often doesn’t have much to do if it’s mutable or not, but maybe that’s just me. I find Setfield.jl works quite nice for me when I want to update immutable structs.
Edit:
I should say, regardless this looks really cool and I think I’m likely to make use of it sometime!
I believe for immutable structs you could use b::Ref{Union{Uninitialized, T}} + custom getproperty like you already have, no? Now that non-isbits structs are often allocated inline, it could have good performance in spite of the Ref.
I like the idea behind the package. I can see myself using it for memoizing computations that may not happen.
struct MyMat
A
@lazy det=LinearAlgebra.det(A)
end
would be a nice syntax for it.
I hope eventually someone will collect all these tricks into one big macro.
@fancy mutable struct Foo{T}
a::T
@lazy b
@read_only c
@cached z=det(a)
end
etc. Common Lisp’s CLOS macro was ridiculous, but it had some really interesting parts.
Probably not just you :). It could be possible to have non-mutating versions of @init!, @uninit! that returns the modified struct and have a @staticlazy or something that doesn’t convert to a mutable struct. I might get around to it some point but if someone needs some PRs for hacktoberfest feel free to work on it
Nice package! Concerning the " Other methods of achieving lazily initialized fields" with “Use a ::Ref{T} field”: I might misunderstand, but this method doesn’t fail 5), i.e. the struct can be immutable?
julia> struct A
b::Ref{Union{Nothing, Int}}
end
julia> struct Uninitialized end
julia> struct B
b::Ref{Union{Uninitialized, Int}}
end
julia> A(2.0)
A(Base.RefValue{Union{Nothing, Int64}}(2))
julia> B(2.0)
ERROR: MethodError: Cannot `convert` an object of type
Float64 to an object of type
Union{Uninitialized, Int64}
Closest candidates are:
convert(::Type{T}, ::T) where T at essentials.jl:205