What is the diffence between abstract type and singleton type

I think both abstract type and singleton type are used for multiple dispatching. So what are their differences and how do I choose?
I know that abstract type cannot be instantialized.

Abstract types are useful if you want to have functions which can operate on a large variety of types.
On the other hand singletons kind of do the opposite, you use them if you want to use dispatch to clarify which method should be used.
As an example, say you have a variety of problems that are related so that some functions can work with all instances of the problem, yet require somewhat different solutions depending on the case.
You might want define an AbstractProblem type and singleton subtypes which allow the user to specify that they have a problem of one particular kind by passing the singleton to a generic solve function

abstract type AbstractProblem end

struct OneProblem <: AbstractProblem end
struct AnotherProblem <: AbstractProblem end

setup(::AbstractProblem) = (;a = 1, b = 2)

function solve(O::OneProblem)
    params = setup(O)
    println("this is one problem")
    return params.a +params.b
end

function solve(O::AnotherProblem)
    params = setup(O)
    println("this is a different problem")
    return params.a *params.b
end

solve(OneProblem())
solve(AnotherProblem())

In this example its of course not really neccessary to do this, but I hope you get the idea

An important difference is that abstract types are abstract, and cannot be instantiated, while singleton types are concrete, and can be instantiated.

Thank you for reply. But why don’t we use abstract type to do dispatch too? I made a minor motification for your example and I don’t know why it is not prefered.

abstract type AbstractProblem end
abstract type OneProblem <: AbstractProblem end
abstract type AnotherProblem <: AbstractProblem end

function solve(::Type{OneProblem})
    println("this is one problem")
end

function solve(::Type{AnotherProblem})
    println("this is a different problem")
end

solve(OneProblem)
solve(AnotherProblem)

Yes, that works too, and both approaches are viable. (You probably know that dispatching on pure types is quite common, such as with zeros(Int, 5)).

But using singleton instances has become a preferred idiom in many cases. See e.g. style guide: Style Guide · The Julia Language

You may also at some point decide that your singleton type should actually carry some parameter value inside it, to provide more information to the function you are calling, and then it is better if you originally used instance dispatch.

This thread may be of interest: Singleton types vs. instances as type parameters

2 Likes