Inheritance in Parametric Types

Friends, I have a small conundrum in terms on code design, and some input is more than welcomed.
So, I’m developing a plotting library, and I have a struct called Marker. Now, the code implements different markers, e.g. Circle, Square and so on. So, I have the following struct:

using Parameters
@with_kw mutable struct JMarker{M}
    fillcolor::Union{String, Color}   = "black"
    strokecolor::Union{String, Color} = "black"
    fillopacity::Real = 0.8
    strokewidth::Real = 0
    strokestyle::String = "solid"
    strokeopacity::Real = 0
    mark::M
end

All good. Now, the issue is the following. When creating a mark, I wish the it could “override” the default fields if the mark has them, e.g.:

@with_kw mutable struct Circle
    fillcolor::Union{String, Color}   = "blue"
    radius::Real = 1
end

m = Marker(mark=Circle())

m.fillcolor ### Should be "blue" and not "black".

Of course, the way that I coded this, it doesn’t work. I could simply do m = Marker(mark=Circle(), fillcolor = "blue"), but this does not seem as an elegant solution. Is there a nice/elegant way of doing this?

The use of Union{String, Color} seems more the wrinkle than the form to keep.

Would you consider unfolding the Union’s purpose?

using Colors

namedcolor(name) =
  (name= name, colorant= parse(Colorant, name))

const Coloring = typeof(namedcolor("white"))
coloring(name) = Coloring(namedcolor(name))

bisque = coloring("bisque")
julia> bisque
(name = "bisque", colorant = RGB{N0f8}(1.0,0.894,0.769))
1 Like

A common pattern is to define a function to fetch the property from the inner struct, like this, instead of copying the data:

julia> using Parameters

julia> @with_kw struct Marker{M}
           mark::M
       end
Marker

julia> @with_kw struct Circle
           fillcolor::String = "blue"
       end
Circle

julia> fillcolor(m::Marker) = m.mark.fillcolor
fillcolor (generic function with 1 method)

julia> m = Marker(mark=Circle())
Marker{Circle}
  mark: Circle


julia> fillcolor(m)
"blue"


Thanks, @JeffreySarnoff . I didn’t know about the parser using Colorant. This is indeed very nice. The {String, Color} thing was annoying ;D
I’ll for sure be adapting my code.

About the “inheritance” question. Any ideas?

Thanks for the reply @lmiq . I’m aware of this convention :slight_smile: The last line in the example was just to point out that I wished to overwrite the “default color” somehow.

1 Like

Note that the example was a possible solution for the “inheritance”. Instead of inheriting the property, just carry it in the inner structure that compose the Marker and use the auxiliary functions to set and fetch that property. (maybe that is just a toy example, but it is strange that the Marker and the mark field can both contain a fillcolor argument, which may be different but must always be the same).

I would avoid repeating the fields, and perhaps solve that with dispatch, using the first argument of the constructor as the marker type, such as:

julia> using Parameters

julia> @with_kw struct Marker{M}
           fillcolor::String = "black"
           mark::M
       end
Marker

julia> @with_kw struct Circle
           radius::Real = 1
       end
Circle

julia> Marker(m::Circle) = Marker(fillcolor="blue", mark=m)
Marker

julia> Marker(m::Circle;kargs...) = Marker(mark=m;kargs...)
Marker

julia> m = Marker(Circle())
Marker{Circle}
  fillcolor: String "blue"
  mark: Circle

julia> m = Marker(Circle(),fillcolor="red")
Marker{Circle}
  fillcolor: String "red"
  mark: Circle

edit: another option, in which the common traits are enclosed in the more specific type, and Marker is abstract:

julia> abstract type Marker end

julia> @with_kw struct MarkerCommonProperties
           fillcolor::String = "black"
       end
MarkerCommonProperties

julia> @with_kw struct Circle <: Marker
           radius::Real = 1
           properties::MarkerCommonProperties = MarkerCommonProperties(fillcolor="blue")
       end
Circle

julia> Marker() = Marker(Circle()) # some default marker
Marker

julia> fillcolor(m::Marker) = m.properties.fillcolor
fillcolor (generic function with 1 method)

julia> Marker(m::T;kargs...) where T = T(kargs...)
Marker

julia> m = Marker(Circle())
Circle
  radius: Int64 1
  properties: MarkerCommonProperties

julia> fillcolor(m)
"blue"

(this one requires some more intricate constructor to set the fields of the common properties cleanly)

2 Likes

Now I get it! Thanks, it makes sense.

Just an up here. Anyone has any better advice here for the OP? I don’t feel comfortable if my answer is taken by others like a good answer here… :grimacing: