Which could be a “julian” way to implement multiple struct defaults?
Like:
abstract type Parameters end
Base.@kwdef struct ParametersDefault1 <: Parameters
x = 5
y = [1,2,3]
w = 10
z = x .+ y
end
Base.@kwdef struct ParametersDefault2 <: Parameters
x = 5
y = [10,20,30]
w = 10
z = x .+ (2 .* y)
end
a = ParametersDefault1(x=1)
b = ParametersDefault2(x=1)
Where the names and types of the fields would be the same between the various “defaults”.
In my case, performances are not much of a concern, as most of the time is then spent in Ipopt.
Perhaps just using different external constructors, altought I still need to explicitly retype the unchanged parameters that are part of other computed defaults:
Base.@kwdef struct Par
x = 5
y = [1,2,3]
w = 10
z = x .+ y
end
function ParDefault1(;kwargs...)
p = Par(;kwargs...)
return p
end
function ParDefault2(;
x = 5,
y = [10,20,30],
z = x .+ (2 .* y) ,
kwargs...)
p = Par(;x=x,y=y,z=z,kwargs...)
return p
end
c = ParDefault1(x=1)
d = ParDefault2(x=1)
What I think is not very “julian” there is the fact that ParDefaults1 (or 2) do not return an instance of the ParDefaults1 type. In this sense the first option is more appropriate.
On the other side, another option is to use a function and types only for dispatch, like:
struct ParDefaults1 end
struct ParDefaults2 end
function set_parameters(::Type{ParDefaults1}; kargs...)
return Par(...)
end function
function set_parameters(::Type{ParDefaults2}; kargs...)
return Par(...)
end
pars = set_parameters(ParDefaults1; x=1)
Thank you. Your solution aside the “same name / dispatch on type” is very similar to my first one.
I am however left with the fact I still need to type p = Par(;x=x,y=y,z=z,kwargs...) where in my real case x, y and z are hundreds (and so are the parameters that don’t change, the “w” in the example).. is there a way I can avoid to type them in the Par constructor ?
The whole idea is to have a struct where I have the same fields but different “defaults” where then users can change something punctual (a few parameters) relative to these “defaults”.
I am fine with your solution, I just need to find now a way to “automatically” get the keyword arguments of a function.
For example, in:
function foo(;
x = 5,
y = [10,20,30],
z = x .+ (2 .* y) ,
kwargs...)
p = printme(;x=x,y=y,z=z,kwargs...)
return p
end
function printme(;kwargs...)
for (k,v) in kwargs
println("$k: $v")
end
end
foo(;x=10, a=1,b="a")
How to avoid typing “x=x,y=y,z=z” ? Is there a macro that provides the keyword arguments (used or not) of a function ?
I can see this old thread, but here I need it inside the function itself, so using that method would lead likely to infinite recursion…
julia> function set_args(;args...)
def_args = Dict(
:x => 1,
:y => 2,
)
for (key, val) in args
if haskey(def_args, key)
def_args[key] = val
else
error("arg $key no found")
end
end
return def_args
end
_foo(args...) = println(args...) # internal
foo(;kargs...) = _foo(set_args(;kargs...)...) # API
foo (generic function with 3 methods)
julia> foo(;x=10)
:y => 2:x => 10
julia> foo(;z=10)
ERROR: arg z no found
Thank you again. Yes, I saw the idea to using a Dict, but in my case(as in the example posted) I have some computed args that depend on the other args (the user can redefine the raw args or the way the computed ones, the one used in the model, are made), and basic dicts don’t work in this case.
How about the following? Just use one struct and use two possible functions to construct it, each of which have default values for all arguments?
abstract type Parameters end
struct ParameterType{X,Y,W,Z} <: Parameters
x::X
y::Y
w::W
z::Z
end
ParametersDefault1(; x = 5, y = [1,2,3], w = 10, z = x .+ y) = ParameterType(x, y, w, z)
ParametersDefault2(; x = 5, y = [10,20,30], w = 10, z = x .+ (2 .* y)) = ParameterType(x, y, w, z)
a = ParametersDefault1(x=1)
b = ParametersDefault2(x=1)
display(a)
display(b)
FWIW, you can type simply foo(;x, y, z, kargs...) to propagate the kargs x,y,z of the input. Appart from that I do not see how to simplify further from what @kwdef gives you.
Nice, I didn’t know that Julia would not complain about “repeated” keyword argument. I still however have the issue of loosing the possibility to compute argument values on the fly based on other arguments, i.e.:
@kwdef struct MyParameters
x = 1
y = 2
z = x+y
end
MyDefaults1 = (x=10, y=20)
MyDefaults2 = (x=1,y=200,z=x+2*y) # This doesn't work
MyParameters(; MyDefaults1...,x=1000,z=3000) # Here Julia doesn't complain about double x keyword and takes 1000
Thanks, I am going to test it…
This works, at the cost to use a small external package for the Struct to NamedTouple convertion…
using NamedTupleTools # for the ntfromstruct() function
@kwdef struct MyParameters
x = 1
y = 2
w = 3
z = x+y
o = x*y
end
@kwdef struct MyDefaults1
x = 10
y = 20
end
@kwdef struct MyDefaults2
x=1
y=200
z=x+2*y
end
function mydefaults1(;kwargs...)
return MyParameters(; ntfromstruct(MyDefaults1())...,kwargs...)
end
function mydefaults2(;kwargs...)
return MyParameters(; ntfromstruct(MyDefaults2())...,kwargs...) # Here Julia doesn't complain about double x keyword and takes 1000
end
mydefaults1(x=1000,z=3000) # (1000,20,3,3000,20000)
x=1; y=3000; o=x+y;
mydefaults2(;x=1,y=3000, o=x+y) # (1,3000,3,401,3001)
If you have variables in the default that depend on other stuff,
why not just have a function at that point?
@kwdef struct MyParameters
x = 1
y = 2
z = x+y
end
MyDefaults1 = (x=10, y=20)
MyDefaults2(; x = 1, y = 200) = (x=x, y=y, z=x+2*y)
# you can even chain defaults:
MyDefaults3(; kwargs...) = MyDefaults2(kwargs..., x = 2)
EDIT: This is the poor man’s version of Gunnar’s suggestion
@kwdef struct MyParameters
x = 1
y = 2
w = 3
z = x+y
o = x*y
end
my_defaults1(; x=10, y=20) = Base.@locals()
my_defaults2(; x=1, y=200, z=x+2*y) = Base.@locals()
function mydefaults1(;kwargs...)
return MyParameters(; my_defaults1()...,kwargs...)
end
function mydefaults2(;kwargs...)
return MyParameters(; my_defaults2()...,kwargs...) # Here Julia doesn't complain about double x keyword and takes 1000
end
mydefaults1(x=1000,z=3000) # (1000,20,3,3000,20000)
x=1; y=3000; o=x+y;
mydefaults2(;x=1,y=3000, o=x+y) # (1,3000,3,401,3001)
Thank you for letting me discover Base.@locals(). Your solution creates a dictionary of local variables that are then iterated with the splat operator… Thank you!