Inner and Outer constructors with Parameters.jl prevent incremental compilation?

Hi all! I am trying to familiarise myself with outer and inner constructors and I would like to know if I am doing something wrong.

I wrote a structure with 1 outer and 1 inner constructor, using also Parameters and Unitful:

using Parameters
using Unitful

@with_kw struct InitialConditions1D{T1<:Array{Float64, 1} , T2 <: Float64}
    function InitialConditions1D(CMg0::T1, CFe0::T1, CMn0::T1, Lx::T2, tfinal::T2) where {T1 <: Array{Float64, 1}, T2 <: Float64}
        if Lx <= 0
            error("Length should be positive.")
        elseif tfinal <= 0
            error("Total time should be positive.")
        elseif size(CMg0, 1) != size(CFe0, 1) || size(CMg0, 1) != size(CMn0, 1)
            error("Initial conditions should have the same size.")
            nx = size(CMg0, 1)
            Δx = Lx / (nx)
            x = range(Δx/2, length=nx, stop= Lx-Δx/2)
            new{T1, T2}(CMg0, CFe0, CMn0, nx, Lx, Δx, x, tfinal)

function InitialConditions1D(;CMg0::Array{Float64, 1}, CFe0::Array{Float64, 1}, CMn0::Array{Float64, 1}, Lx::Unitful.Length, tfinal::Unitful.Time)
    InitialConditions1D(CMg0, CFe0, CMn0, convert(Float64,ustrip(u"m", Lx)), convert(Float64,ustrip(u"Myr",tfinal)))

The idea is simply to have the outer constructor to convert the units from the input, and the inner constructor to check the inputs and to create some variables for the initial conditions of a numerical model. I use Parameters here just to have nice printing and unpacking.

So, with a simple input, I can run this:

CMg = ones(5)
CFe = ones(5)
CMn = ones(5)
Lx = 10.0u"km"
tfinal = 1.0u"Myr"

IC1D = InitialConditions1D(CMg0=CMg, CFe0=CFe, CMn0=CMn, Lx=Lx, tfinal=tfinal)

which works nicely. The problem is that when I put this code into a package and Precompiled it, I obtain this warning:

WARNING: Method definition (::Type{DiffusionGarnet.InitialConditions1D{T1, T2} where T2<:Float64 where T1<:Array{Float64, 1}})() in module DiffusionGarnet at Parameters.jl:545 overwritten at initialconditions.jl:32.
  ** incremental compilation may be fatally broken for this module **

It seems happening because I create two different instances of constructors. Is that behaviour expected and is it something I should be worried about? It doesn’t seem to occure if I remove the @with_kw from Parameters.jl and use a normal structure instead. Is there another way to properly write that piece of code?

IIRC then @with_kw (see @kwdef for a Base alternative) is used to define default constructors for structs and they do this by defining zero argument outer constructors with all struct elements added as keyword arguments to that function.
In your case this means that @with_kw already defines InitialConditions1D(;CMg0::Array{Float64, 1}, CFe0::Array{Float64, 1}, CMn0::Array{Float64, 1}, Lx::Unitful.Length, tfinal::Unitful.Time) for you.
The real problem now is that Julia does not dispatch on keyword arguments (to avoid combinatoric explosions of number of methods), hence, the warning.

So one way to solve that while keeping @with_kw would be to convert the keyword arguments of your outer constructor into normal arguments such that the outer constructor is of the same form as the inner one.

Another way to work around this would be to remove the @with_kw, in case the outer constructor you apply is the only one you want to support.

Side note: That the keyword arguments are ignored for dispatch does not mean that type annotations are ignored, instead they still apply whenever you try to set a keyword.

1 Like

Thx for the reply, that makes sense :ok_hand: