# When not to use Multiple Dispatch?

**URL:** <https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792>\
**Category:** General Usage\
**Tags:** multidispatch\
**Created:** [March 31, 2022, 9:49am UTC](https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792 "2022-03-31T09:49:39Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![helgee](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/helgee/32/2022_2.png) [@helgee](https://discourse.julialang.org/u/helgee)\
**Post date:** [March 31, 2022, 9:49am UTC](https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792/1 "2022-03-31T09:49:39Z")

</div>

Hi folks!

On my endless quest to make [AstroTime.jl](https://github.com/JuliaAstro/AstroTime.jl) the best date and time handling package in the universe, I am pondering another redesign 🙈

When modelling my problem domains, I always gravitate to pushing a lot of information into the type system. Let’s have a look at an example:

```julia
# We want to model `DateTime`-like objects in different time scales
abstract type AbstractTimeScale end

struct TAI <: AbstractTimeScale end
struct UTC <: AbstractTimeScale end

# Now our epochs can have a `scale` field but need to be parametric
struct Epoch{T<:AbstractTimeScale}
    scale::T
    # ...
end

```

In principle, this seems elegant because you can easily dispatch on different `AbstractTimeScale` subtypes. There are some less than ideal side effects though.  
First of all there is a proliferation of parametric types, e.g., a state vector type which uses `Epoch` now needs to have a type parameter for the `AbstractTimeScale` to remain concrete. The dynamic dispatch also seems to have a performance impact (?). Another issue is that handling collections of different subtypes of `AbstractTimeScale` is painful and requires use of `@generated` functions and lots of type annotations in my real world code to remain performant, which I consider a code smell.  
Finally, a silly thing but something that bothers me is the fact that this coding style requires lots of empty parens, e.g., `Epoch(..., UTC())`. Feels unintuitive…

**TL;DR:** Elegant in theory but complex in practice.

A simpler design could just use `Symbol` for the time scale, e.g.:

```julia
type Epoch
    scale::Symbol
end

```

You lose the benefits of multiple dispatch and need to write “branchy” code. It also makes it harder to make the system user-extensible.

```julia
function convert_epoch(ep, scale)
    if ep.scale === :TAI
        if scale === :UTC
            tai_to_utc(ep)
       elseif ...
       ...
       end
    end
end

```

On the other hand, you do not need to deal with type parameters and generated functions and everything remains concretely typed.

I am not quite sure where I am going with this TBH. I feel like maybe we miss something like Rust’s `enum` in Julia, i.e., some form of [sum types](https://github.com/MasonProtter/SumTypes.jl).  
Anyhow, how do you approach these things? Am I overthinking this? Are there situations where you avoid multiple dispatch?

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [March 31, 2022, 10:16am UTC](https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792/2 "2022-03-31T10:16:16Z")

</div>

What you’re saying makes sense to me. If a type or type parameter isn’t known at compile-time or there are a ton of them to compile for, then there probably isn’t much benefit compared to refactoring runtime-checked types as instances of types known at compile-time.

I agree that long if-else statements aren’t feasibly extensible, my go-to is a `const` global dictionary mapping symbols to functions (the `const` makes the variable “constant”, the dictionary itself is still mutable), but there might be better methods. If possible, I would try function barriers (check Performance Tips for an example) to minimize the impact of the type instability from indexing such a dictionary. However, that doesn’t always help e.g. a for-loop that needs a different function each iteration.

Take some time to consider the expected use cases of your package before planning the refactoring, I wouldn’t want to throw away performant code over collections of 1 subtype of `AbstractTimeScale` if I would need to work with a lot of them.

---

<div class="post-metadata">

**Author:** ![helgee](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/helgee/32/2022_2.png) [@helgee](https://discourse.julialang.org/u/helgee)\
**Post date:** [March 31, 2022, 10:38am UTC](https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792/3 "2022-03-31T10:38:14Z")

</div>

Another way to deal with the extensibility problem is to offer a `Val`-based escape hatch.

```julia
function convert_epoch(ep, scale)
    if ep.scale === :TAI
         if scale === :UTC
            tai_to_utc(ep)
        elseif ...
            ...
        else
            convert_epoch(ep, Val(ep.scale), Val(scale))
        end
    end
end

```

Then the user can provide a method for `convert_epoch(ep, ::Val{:TAI}, ::Val{:Foo})`.

---

<div class="post-metadata">

**Author:** ![gcalderone](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gcalderone/32/1539_2.png) [@gcalderone](https://discourse.julialang.org/u/gcalderone)\
**Post date:** [March 31, 2022, 11:25am UTC](https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792/4 "2022-03-31T11:25:58Z")

</div>

I’m not sure if I understand correctly, but maybe the issue is pretending that `Epoch` objects built using UTC and TAI are different types, while they actually are exactly the same entity (i.e. a well defined instant in time). Hence, an alternative approach may be to chose a reference frame (e.g. UTC with, say, milliecond precision) and use it as an internal representation for all `Epoch` objects to be used for calculations:

```julia
struct Epoch
    UTC_ms::Int64 # ms since, e.g., 00:00:00 UTC on 1 January 1970
end

```

Then you may have `EpochRepresentation{T<:AbstractTimeScale}` objects which are just representations of an `Epoch` object on a specific time scale, possibly rendered as a string:

```julia
abstract type AbstractTimeScale end
struct TAI <: AbstractTimeScale end
struct UTC <: AbstractTimeScale end
struct EpochRepresentation{T<:AbstractTimeScale}
    string::String
end

```

By doing so `Epoch` would already be concrete by itself, and you should only implement the conversion routines for I/O against files or terminals, possibly extending the `convert` function as discussed [here](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Conversion-vs.-Construction).

---

<div class="post-metadata">

**Author:** ![helgee](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/helgee/32/2022_2.png) [@helgee](https://discourse.julialang.org/u/helgee)\
**Post date:** [March 31, 2022, 11:34am UTC](https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792/5 "2022-03-31T11:34:03Z")

</div>

> [@gcalderone](#):
>
> Hence, an alternative approach may be to chose a reference frame (e.g. UTC with, say, milliecond precision) and use it as an internal representation for all `Epoch` objects to be used for calculations:

That is an approximation which only holds if you are close to the geoid where TAI and UTC are defined. If you also need to consider relativistic time scales, you cannot use an absolute reference and two points in time in different time scales are not comparable. I go into the details in my [JuliaCon talk](https://www.youtube.com/watch?v=HGlsRoy1JxU&t=2s) from last year. Have a look if you are interested 😁

---

<div class="post-metadata">

**Author:** ![gcalderone](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gcalderone/32/1539_2.png) [@gcalderone](https://discourse.julialang.org/u/gcalderone)\
**Post date:** [March 31, 2022, 11:54am UTC](https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792/6 "2022-03-31T11:54:06Z")

</div>

> [@helgee](#):
>
> If you also need to consider relativistic time scales, you cannot use an absolute reference and two points in time in different time scales are not comparable.

Definitely, correct! But in this case it is no longer sufficient to specify UTC, TAI or whatever… you’ll also need information concerning an intertial frame.

The topic is definitely interesting and I’ll have a look to your talk. Thanks!

---

<div class="post-metadata">

**Author:** ![stepanzh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stepanzh/32/34851_2.png) [@stepanzh](https://discourse.julialang.org/u/stepanzh)\
**Post date:** [March 31, 2022, 1:40pm UTC](https://discourse.julialang.org/t/when-not-to-use-multiple-dispatch/78792/7 "2022-03-31T13:40:55Z")

</div>

Add some ideas, perhaps, some of them would be helpful.

```julia
#==== Initial version =====#

abstract type AbstractTimeScale end

struct TAI <: AbstractTimeScale end
struct UTC <: AbstractTimeScale end

struct Epoch{T<:AbstractTimeScale}
    scale::T
    # ...
end

Epoch(x..., ::Type{T}) where {T<:AbstractTimeScale} = Epoch(x..., T())

# Conversion - OK
convert(::Type{Epoch{TS1}}, y::Epoch{TS2}) where {TS1,TS2} = error("Not implemented")

# Array of Epochs
# Nope, need to promote to smth common first, e.g. UTC, but conversion may do it automatically

# Init - OK
# See constructor above

#==== Version with constants =====#

struct TimeScale
    x::Symbol
    # meta information etc
end

const TAI = TimeScale(:tai)
const UTC = TimeScale(:utc)

struct Epoch
    scale::TimeScale
    # ...
end

# Conversion
# "Branchy"?

# Array of Epochs - OK
# Epoch now is concrete

# Init - OK
Epoch(..., UTC)

#==== Topsy-turvy =====#

abstract type Epoch end

# Common interface
value1(ep::Epoch) = ep.val1
value2(ep::Epoch) = ep.val2
# ...

struct UTC <: Epoch
    # ...
end

struct TAI <: Epoch
    # ...
end

# Conversion - OK
convert(x::E1, y::E2) where {E1,E2} = error("Not implemented")

# Array of Epochs
# Nope, need to promote to smth common first, e.g. UTC, but conversion may do it automatically

# Init - OK
UTC(...)
TAI(...)

```
