Set struct's parameters using a function whose first argument is for the struct

Hi, I’m trying to reuse a function to set parameters of a struct.
For example, I have a function whose the first argument should have type MyStruct:

function set_parameter(mystruct::MyStruct)
    mystruct.a = 1
end

And I want to use it to set some parameters of the following struct.

mutable struct MyStruct
    ???? = set_parameter(????)
    b::Int
end

The reason why I’m trying to do this is to reuse the function, which is that I used to do in Python.

Is there any way to resolve this?

mutable struct MyStruct
    a

    function MyStruct(a)
        s = new();
        set_a!(s, a);
        return s
    end
end

set_a!(s :: MyStruct, a) = (s.a = a);
2 Likes

Perhaps this?

mutable struct MyStruct
    a
    b::Int
end
set_parameter(mystruct::MyStruct) = mystruct.a
MyStruct(s :: MyStruct,b :: Int64) = MyStruct(set_parameter(s),b)
s1 = MyStruct(1,2)
s2 = MyStruct(s1,3)

I do not understand why you are trying to call a method inside a struct definition. Are you trying to use default struct values they are not yet implemented but are to be a feature in the future.

Does Parameters.jl not solve your problems?

@Henrique_Becker
Not in this case.
For example, I wanna solve optimisation problem with initial guess. In this case, I would like to set an initial value of the optimisation problem using function like initial_guess! (outside the module).
Also, I sometimes wanna use a default value as an initial value (that is, being lazy).

To do so, I usually write Python codes like this (sorry for this awful code; I’m typing this via my cellphone, and this may be a wrong code):

class ABC():
    def __init__(self):
        self.p = set_initial_value(0.)
        
    def set_initial_value(self, s):
        self.p = s

Actually, @hendri54 's solution is what I needed.

That can be done through multiple dispatch:

mutable struct Foo
    a
    b
end

Foo(b) = Foo(1, b)

You now have two constructors, one needs both a and b and one only b.

1 Like

Nothing against other solutions, but it seems that for what you want the suggestion of Henrique would provide a clearer code:


julia> using Parameters

julia> @with_kw mutable struct S
           a :: Float64 = 0.
           b :: Float64 = 1.
       end
S

julia> initial_value = S()
S
  a: Float64 0.0
  b: Float64 1.0


julia> initial_value = S(a = 5.)
S
  a: Float64 5.0
  b: Float64 1.0


julia> initial_value = S(a = 5., b = 3.)
S
  a: Float64 5.0
  b: Float64 3.0

julia> initial_value = S(a = initial_value.a)
S
  a: Float64 5.0
  b: Float64 1.0


and you could additionally define

julia> S(s::S) = S((getfield(s,field) for field in fieldnames(S))...)
S

julia> S(initial_value)
S
  a: Float64 5.0
  b: Float64 1.0


1 Like

What’s best seems to depend on whether or not the set_parameter method truly requires a (partially initialized) MyStruct as input (as the OP specified the way I understood it). If not, then Parameters.jl is certainly the way to go.

3 Likes

I was just playing a little bit with that problem, and this is a general solution, for any number of parameters. You can set any of the fields “by hand”, and the others will be filled up by the values of the previous input structure:

struct S
  a
  b
end

function S(s :: S; opt...)
  opt_keys = keys(opt) 
  opt_values = values(opt)
  pars = []
  for field in fieldnames(S)
    if ! (field in opt_keys)
      append!(pars,getfield(s,field))
    else
      append!(pars,opt_values[field])
    end
  end
  return S(pars...)
end

s = S(0,0)

@show s

@show S(s,b=1)

@show S(s,a=1)

@show S(s,a=1,b=1)    

Results:

s = S(0, 0)
S(s, b = 1) = S(0, 1)
S(s, a = 1) = S(1, 0)
S(s, a = 1, b = 1) = S(1, 1)

1 Like

For a generic solution for changing immutables (and also mutables), I highly recommend Setfield.jl. It is useful, especially for nested immutables. You don’t need to understand anything complex for just using it, but if you want to know the idea behind Setfield.jl, this JuliaCon talk is a great introduction:

2 Likes