Refactoring Julia Modules for Shape Representation and Canvas Drawing

I have two Julia modules, “Shape.jl” and “Canvas.jl”, which define shapes and a canvas for drawing them. The code works as intended, but I’m wondering if there’s a cleaner and more efficient way to structure these modules.

Shape.jl module:

module ShapeMod
export Shape, Rectangle, Circle, Refine!

abstract type Shape end

mutable struct Rectangle <: Shape
  Colour::String
  Width::Real
  Height::Real
  Rectangle(;Colour::String="black", Width::Real=.5, Height::Real=.5) = new(Colour, Width, Height)
end

function Refine!(Rect::Rectangle;Colour::String=Rect.Colour, Width::Real=Rect.Width, 
  Height::Real=Rect.Height)
  Rect = Rectangle(Colour, Width, Height)
end

mutable struct Circle <: Shape
  Colour::String
  Radius::Real
  Circle(;Colour::String="black", Radius::Real=.5) = new(Colour, Radius)
end

function Refine!(Cir::Circle;Colour::String=Cir.Colour, Radius::Real=Cir.Radius)
  Cir = Circle(Colour, Radius)
end

end #module

Canvas.jl module:

module CanvasMod

include("ShapeMod.jl")
import .ShapeMod: Shape, Rectangle, Circle, Refine!

export Canvas, Refine!, RefineCanvas!

mutable struct Canvas
  CanvasShape :: Shape
  XPosition :: Real
  YPosition :: Real
  Canvas(;CanvasShape::Shape=Rectangle(), XPosition::Real=0, YPosition::Real=0) = new(CanvasShape, XPosition, YPosition)
end

function Refine!(Canv::Canvas; CanvasShape::Shape=Canv.CanvasShape, XPosition::Real=Canv.XPosition, YPosition::Real=Canv.YPosition)
  Canv = Canvas(CanvasShape, XPosition, YPosition)
end

function RefineCanvas!(Canv::Canvas; CanvasShape::String=typeof(Canv.CanvasShape), Colour::String=Canv.CanvasShape.Colour, Width::Real=Canv.CanvasShape.Width, Height::Real=Canv.CanvasShape.Height, Radius::Real=Canv.CanvasShape.Radius,  XPosition::Real=Canv.XPosition, YPosition::Real=Canv.YPosition)

  CanvShape = getfield(Canv, :CanvasShape)
  if(CanvasShape == Rectangle)
    Refine!(CanvShape, Colour, Width, Height)
  elseif(CanvasShape == Circle)
    Refine!(CanvShape, Colour, Radius)
  end

  Canv = Refine!(Canv, CanvShape, XPosition, YPosition)

end

end #module

Is there a more concise and idiomatic way to define these modules, especially regarding the constructors and the Refine! functions? Any suggestions for improving the structure and readability of the code would be appreciated.

One thing:

mutable struct Canvas
  CanvasShape :: Shape

Shape is abstract type, so this struct is type-unstable. Use, better:

mutable struct Canvas{S<:Shape}
  CanvasShape :: S

So to make it parametric for the shape type.

Then, you can use dispatch easier to define your RefineCanvas! function

function RefineCanvas!(Canv::Canvas{Rectangle}; etc...) 
    Refine!(Canv.CanvShape, Colour, Width, Height)
    Canv = Refine!(Canv, CanvShape, XPosition, YPosition)
    return Canv
end

function RefineCanvas!(Canv::Canvas{Circle}; etc...) 
    Refine!(Canv.CanvShape, Colour, Radius)
    Canv = Refine!(Canv, CanvShape, XPosition, YPosition)
    return Canv
end

as a styling note, usually variables are written in lowercase, and uppercase is used to identify types.

1 Like