Hello everybody,
I have a bunch of questions concerning parametric constructors for structs.
Some background: I was writing a toy code to compute a Newton fractal. For that it makes sense to bundle the function of which the roots are computed, its derivative and the locations of the roots (and some more parameters) into a struct.
After reading this discourse thread and then by trial-and-error I came up with the following (reduced for this thread) code:
struct fractal{F<:Function,G<:Function}
xx
yy
myfunction::F
derivative::G
number_of_roots::Int
roots
end
function make_fractal(xx,yy)
function myfunction(z::Complex{Float64})
return z^3-1.0
end
function derivative(z::Complex{Float64})
return 3.0*z^2
end
roots = (complex(1.0,0.0),
complex(-0.5,0.5*sqrt(3)),
complex(-0.5,-0.5*sqrt(3)) )
return fractal(xx,yy,myfunction,derivative,3,roots)
end
xx=range(-3.5, stop=2.0, length=2000)
yy=range(-2.5, stop=2.5, length=2000)
Fractal=make_fractal(xx,yy)
function iterationstep(Fractal::fractal,z)
return z-Fractal.myfunction(z)/Fractal.derivative(z)
end
zz=complex(1.0,2.0)
zz=iterationstep(Fractal,zz)
This works nicely in the sense that @code_warntype
is completely happy and myfunction() and derivative() get inlined:
julia> @code_warntype iterationstep(Fractal,zz)
Body::Complex{Float64}
31 1 ─ %1 = (Base.getfield)(z, :re)::Float64 │╻╷╷╷╷╷ myfunction
│ %2 = (Base.getfield)(z, :re)::Float64 ││╻ literal_pow
│ %3 = (Base.mul_float)(%1, %2)::Float64 │││╻ *
│ %4 = (Base.getfield)(z, :im)::Float64 ││││╻ *
│ %5 = (Base.getfield)(z, :im)::Float64 │││││╻ imag
...
...
(Remark: having myfunction::Function
and derivative::Function
, i.e. unparametrized fields, in the struct above and calling iterationstep(Fractal,zz)
as above disables type inference as alrady noted in the mentioned discourse thread).
Unfortunately I have only a vague understanding what I did and why this works. Reading the manual section gives me the impression that I should have specified parametric types for all fields in the struct ? But I have untyped fields in there. How does julia decide what fields in the constructor call
fractal(xx,yy,myfunction,derivative,3,roots)
are F
and G
? By position ? Then what about multiple Float64
or Int
or Vector{Float64}
fields ? Or is this abstract versus concrete types ?
Also, changing the struct to
struct fractal{F<:Function}
xx
yy
myfunction::F
derivative::F
number_of_roots::Int
roots
end
which would appear to be reasonable as myfunction()
and derivative()
have the same argument and return type throws an error
julia> include("mwe.jl")
ERROR: LoadError: MethodError: no method matching fractal(::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::getfield(Main, Symbol("#myfunction#3")), ::getfield(Main, Symbol("#derivative#4")), ::Int64, ::Tuple{Complex{Float64},Complex{Float64},Complex{Float64}})
Closest candidates are:
fractal(::Any, ::Any, ::F<:Function, ::F<:Function, ::Int64, ::Any) where F<:Function at /mnt/ramfs/mwe.jl:2
Stacktrace:
[1] make_fractal(::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}) at /mnt/ramfs/mwe.jl:21
[2] top-level scope at none:0
which I do not understand.
On top, would it be advisable to actually type the un-typed fields ? How would I do that for fractal.xx
and fractal.roots
(one could switch fractal.roots
to a vector from a tuple), please note that a different myfunction()
might of course a different number of roots ?
I am aware that in the already mentioned thread the suggestion was to use something like
struct fractal
xx
yy
myfunction::Function
derivative::Function
number_of_roots::Int
roots
end
...
...
function iterationstep(f,d,z)
return z-f(z)/d(z)
end
zz=iterationstep(Fractal.myfunction,Fractal.derivative,zz)
which of course works fine and infers type/ inlines correctly.
Still, I would appreciate any help to understand the parametric constructors better.
Thank you very much for reading this far !
Best Regards