# Function with a single method acting on Union{T1, T2} or another function with two methods, for T1 and T2?

I have the following code:

``````struct Square{T<:Real}
width::T = 1
end

struct Rectangle{T<:Real}
width::T = 1
height::T = 2
end

if q isa Square
return q.width^2
elseif q isa Rectangle
return q.width * q.height
end
end

function area2(q::Square)
q.width^2
end

function area2(q::Rectangle)
q.width * q.height
end
``````

When I use `@code_warntype`, upon the same concrete Quadrilateral, the output for the `area1` function, which uses the Union{Square, Rectangle} type, is longer and seems to be a little more contrived than the corresponding output for the `area2` function. Using a naive benchmarking does not, however, seem to give appreciable difference.
Which strategy should I follow/prefer, taking into account better code legibility and performance? Right now, I am inclined to using `area2`, with its specific methods, according to the different calling signatures; does that sound reasonable?

This is similar to

Generically, the `area2` approach has two benefits:

• it can be expanded to additional shapes
• it guarantees compile time dispatch (if the rest of the code is type stable)
Perhaps the logical approach would be to set up `Square` and `Rectangle` as subtypes of `Quadrilateral`. But that depends on what else you are doing with those types.
3 Likes

Both versions should perform identically, you are just viewing the typed IR before optimization. They actually produce the same optimized code for me:

``````julia> @code_warntype area1(Square(1))
Variables
#self#::Core.Compiler.Const(area1, false)
q::Square{Int64}

Body::Int64
1 ─ %1 = (q isa Main.Square)::Core.Compiler.Const(true, false)
│        %1
│   %3 = Base.getproperty(q, :width)::Int64
│   %4 = Core.apply_type(Base.Val, 2)::Core.Compiler.Const(Val{2}, false)
│   %5 = (%4)()::Core.Compiler.Const(Val{2}(), false)
│   %6 = Base.literal_pow(Main.:^, %3, %5)::Int64
└──      return %6
2 ─      Core.Compiler.Const(:(q isa Main.Rectangle), false)
│        Core.Compiler.Const(:(%8), false)
│        Core.Compiler.Const(:(Base.getproperty(q, :width)), false)
│        Core.Compiler.Const(:(Base.getproperty(q, :height)), false)
│        Core.Compiler.Const(:(%10 * %11), false)
│        Core.Compiler.Const(:(return %12), false)
└──      Core.Compiler.Const(:(return), false)

julia> @code_warntype optimize=true area1(Square(1))
Variables
#self#::Core.Compiler.Const(area1, false)
q::Square{Int64}

Body::Int64
1 ─ %1 = Base.getfield(q, :width)::Int64
│   %2 = Base.mul_int(%1, %1)::Int64
└──      return %2

julia> @code_warntype optimize=true area2(Square(1))
Variables
#self#::Core.Compiler.Const(area2, false)
q::Square{Int64}

Body::Int64
1 ─ %1 = Base.getfield(q, :width)::Int64
│   %2 = Base.mul_int(%1, %1)::Int64
└──      return %2
``````
2 Likes

One of approaches here, since both structs are immutable, is to define accessor functions `width` and `height` and make a single generic function `area` using accessors:

``````width(q::Union{Rectangle, Square}) = q.width
height(q::Square) = q.width
height(q::Rectangle) = q.height

area(q::Union{Rectangle, Square}) = width(q) * height(q)
``````
4 Likes

For clarification: are you asking about the generic pattern or about the actual code that you posted? I think this will affect the answer.

@hendri54: I do not understand exactly what you mean… At any rate, any further comments are much welcome. Your former reply helped a lot and I read the link you mentioned there.

Likewise, I enjoyed the flag optimize=true to @code_warntype which @simeonschaub used in his post.

I asked because some replies (especially mine) discussed the general pattern (similar to the thread I pointed to earlier), while others discussed how to specifically calculate areas.
This matters, for example, for the question whether one can rely on compiler optimization to remove the unused branch in each call to `area1`.

My actual post was only a minimal working example (MWE). In fact, I am using that generic pattern in a larger code; I am not particularly concerned with this naive area example, but I think it might make things stripped down to the essential matter… I would like to hear any more generic comments or pointers anyone might deem relevant