Subtyped parameters in constructors

I was playing around with vague constructors and I noticed some very interesting and useful behavior of arrays. I can use “subtype” notation in array constructors!

julia> S = Diagonal(randn(5))
5×5 Diagonal{Float64, Vector{Float64}}:
 0.324104    ⋅         ⋅         ⋅        ⋅
  ⋅        -0.561848   ⋅         ⋅        ⋅
  ⋅          ⋅        1.56105    ⋅        ⋅
  ⋅          ⋅         ⋅       -1.21263   ⋅
  ⋅          ⋅         ⋅         ⋅       2.01912

julia> Array{<:Any,2}(S)
5×5 Matrix{Float64}:
 0.324104   0.0       0.0       0.0      0.0
 0.0       -0.561848  0.0       0.0      0.0
 0.0        0.0       1.56105   0.0      0.0
 0.0        0.0       0.0      -1.21263  0.0
 0.0        0.0       0.0       0.0      2.01912

How can I make my own structs do this? For example, I have a type called Quantity:

struct Quantity{T<:Any,U<:AbstractUnitLike}
    value :: T
    units :: U
end

I would like to get this behaviour if I do something like what follows instead of getting this error:

Quantity{Float64, <:AffineUnits}(v, u)
ERROR: MethodError: no method matching (Quantity{Float64, <:AffineUnits})(::Float64, ::AffineUnits{Dimensions{FixedRational{Int32, 25200}}})
The type `Quantity{Float64, <:AffineUnits}` exists, but no method is defined for this combination of argument types when trying to construct it.
Stacktrace:
 [1] top-level scope
   @ REPL[20]:1

How do I define the behaviour of a broader class like
Quantity{Float64, <:AbstractUnits}(v, u)
without defining a whole bunch of methods?

Just an update, I managed to get some of my desired behaviour with this:

struct Quantity{T<:Any,U<:AbstractUnitLike}
    value :: T
    units :: U
    function Quantity{T,U}(v0::T0, u0::U0) where {T,U,T0,U0}
        u = u0 isa U ? u0 : convert(U, u0)
        v = v0 isa T ? v0 : convert(T, v0)
        return new{typeof(v), typeof(u)}(v,u)
    end
    Quantity(v,u) = new{typeof(v), typeof(u)}(v,u)
end

In this way, I can get my desired behaviour using

Quantity{Float64, AbstractUnits}(1.2, u"kg")

I was wondering how to make something like this work for

Quantity{Float64, <:AbstractUnits}(1.2, u"kg")

Not sure why you want this, but one approach is to dispatch on all subtypes of your type. For correctness, you may need to keep in mind that Union{} subtypes all types.

function (::Type{Q})(v, u) where {Q <: Quantity}
    # constructor code here
end

Also keep in mind there’s currently no generic way to access a bound (either lower or upper) of a type variable, see:

Also see this section in the Manual: Constructors are just callable objects

Thanks, that pattern helped me with some other things, but on this behavior I think you’re right. This isn’t needed, and is less useful than I thought it would be. Conversions really should be specifying concrete types if that’s what you want returned.

1 Like