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:
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?
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.
Thanks for the reply @lmiq . I’m aware of this convention The last line in the example was just to point out that I wished to overwrite the “default color” somehow.
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)