mauro3/Parameters.jl

Provide hooks to massage data after creation

Opened this issue · 12 comments

If you have mutable structs, you can have fields you want to set before or after the constructor Parameters.jl uses.

Is there a way to provide access to wrapping that function?

Maybe with @before_create and @after_create (hook) macros?

I don't quite understand. Would it be solved with "Calculated fields" #18?

Also note that if you specify the positional constructor (for all fields) then the kw-constructur will run through that. That may allow to manually program what you're after.

@mauro3
MWE would be a way to add finalizers to constructed mutable structs

But can't you provide finalizers with just registering a function: https://docs.julialang.org/en/stable/stdlib/base/#Base.finalizer: finalizer(::MyStruct, finalfn)? I don't see how adding macro magic to that would make it more concise or more readable.

When do you add a finalizer? Usually at object creation time.
Otherwise I have to manually add a finalizer every time I construct a new object.

Thanks, now I get it. I think the post-create hook could be ok to implement as that would be a hook of a function of one argument. The pre-create hook however (I think) would need to be a function which has all fields as positional arguments, so it would not be any easier to write than the positional constructor.

So, for the post-create hook

@macroexpand @with_kw struct A
    a=1
    b=2
    @post_create a -> finalizer(a, finalfn) # post_create_fn =  a -> finalizer(a, finalfn)
end

struct A
    a
    b
    A(; a=1, b=2) = begin
        A(a, b)
    end
    A(a, b) = begin
        post_create_fn(new(a, b))
    end
end

I never had an application for this so it's low on my priority list. But a PR, if it does not add too many LOCs, would be welcome.

Current workaround is making data structure have one not-parameterized field and then using a base constructor that accepts at least one argument (i.e. the non-parameterized one)

This is related to the following line of code:

I don't understand that work-around, maybe you can post a MWE. What do you mean with "base constructor"? An inner or outer constructor?

If the pre-create hook would be just a block of code to be inserted into the positional inner constructor, then it would have access to all variables. Something like:

@macroexpand @with_kw struct A
    a=1
    b=2
    @pre_create begin
       println("Before `new`")
       a = a+1
    end
    @post_create a -> finalizer(a, finalfn) # post_create_fn =  a -> finalizer(a, finalfn)
end

struct A
    a
    b
    A(; a=1, b=2) = begin
        A(a, b)
    end
    A(a, b) = begin
        begin
           println("Before `new`")
           a = a+1
        end
        post_create_fn(new(a, b))
    end
end

Unfortunately, this makes the pre and post hooks asymmetric...

Another use case from #72 is to add a deprecation warning to a field:

struct AB
    a
    b
end

function AB(a,b)
    depwarn("b is deprecated", :AB)
    return AB(a)
end

I don't think having symmetric pre/post hooks is that important?

One ultimate use case might be something like:

@with_kw struct Wire
  ...
  eta_cu = 1424
  eta_ss = 137
  w = 15
  ...
  dr = h_C
  N = round( ( r_2 - r_1 ) / dr - 1 )
  s_0 = 0.5 * ( r_1 + r_2 )
  ...
  function f_s(a,s)
      ...
  end

  function eta_para(J_para,a,s)
      ...
  end
  ...
end

I'm a bit confused by the question you ask yourself.

The example you give seems closer to calculated fields, no? Or what is a and s?

This would also be the way to generalize what macros (and other code) do inside the type-def. Currently @assert is special cased, such that it gets added to the constructor. Could there be a general way to do this?