Yet another idea: implement insertion sort in type space.
First, we need a function that sorts pairs. For n types, one can just list n(n-1)/2 combinations if that is not excessive, using symmetry:
_pairsort(a::T, b::T) where T = (a, b)
_pairsort(a::TypeA, b::TypeB) = (a, b)
_pairsort(a::TypeA, c::TypeC) = (a, c)
_pairsort(b::TypeB, c::TypeC) = (b, c)
_pairsort(a, b) = (b, a)
If this is excessive, because we have too many types, an alternative implementation can be
_rank(::TypeA) = 1
_rank(::TypeB) = 2
_rank(::TypeC) = 3
_pairsort(a, b) = _rank(a) > _rank(b) ? (b, a) : (a, b)
which requires n methods for n types. All that matters is that this is type stable.
Then a simple insertion sort:
_insert(a) = (a, )
_insert(a, b) = _pairsort(a, b)
function _insert(a, b, c...)
A, B = _pairsort(a, b)
(A, _insert(B, c...)...)
end
_sort(sorted) = sorted
function _sort(sorted, a, unsorted...)
_sort(_insert(a, sorted...), unsorted...)
end
canonical_order(args...) = _sort((), args...)
as it is resolved at compile time anyway. Everything is type stable and quite simple.