Should I dispatch on singleton types or their instances?

When I am developing packages, I often come across the following problem.

struct Step{T} end

func(::Step{1}, x) = do something...
func(::Step{2}, x) = do some different things...

I am not sure should I use singleton types Type{Step{1}} and Type{Step{2}}, instead of singletons Step{1} and Step{2} to dispatch behaviors?

func(::Type{Step{1}}, x) = do something...
func(::Type{Step{2}}, x) = do some different things...

Julia’s doc says instances should be used, is it a general rule or just for Val type? Because I have seen both of them be used in different packages:

  1. Using instances (like Bisection())
using Roots

# struct Bisection <: AbstractBisection end  # either solvable or A42
f(x) = exp(x) - x^4

# a bisection method has the bracket specified with a tuple or vector
julia> find_zero(f, (8,9), Bisection())
  1. Using singleton types (like Newton):
using IntervalRootFinding

roots(log, -2..2, Newton)
  1. Which one is a better practice?
  2. Can those types (Step, Bisection, Newton) be called “traits”?
  3. If we use singleton types (Type{Step}), should I just let the type Step be an abstract type, instead of a concrete type?

Often the context suggests a preferred method: eg if the type is a container but is meaningful for some method without its contents (eg convert), or the contents are not relevant for some kind of information (Base.IteratorSize), or no value is available at some point (eg you are just creating an instance).

When in doubt, I would go for instances, not types. YMMV.

I think that these patterns now transcend the original idea of traits, but I guess one could still call them traits.

I agree, you never know when you want to add options to the type in the form of fields


Sorry I do not quite get it. You mean at these circumstances we should use types rather than instances?

Yes, I suggested these as examples where using types makes more sense.

Related: Singleton types vs. instances as type parameters


FWIW the style guide in the manual recommends using instances: