Types vs traits for dispatch

I recently learned about the Tim Holy Trait Trick as one way to dispatch on “groups” of types outside of a type hierarchy.

This seems more general to me than dispatch based on type hierarchy, but I’m not sure. Are there things which are easier to do (or settings which are better suited for) using type hierarchy dispatch instead of only traits?

2 Likes

Julia’s type system together with multiple dispatch is already very powerful for generic code and should be able to handle the majority of cases, so it’s more like traits are an overkill for most tasks. For example, instead of defining a new trait for two types to dispatch on, you might as well just rely on multiple dispatch and define a method for each type.
That being said, traits are definitely more general and sometimes necessary when you need to extend the type hierarchy in an orthogonal direction (see the original discussion where THTT first comes up). They’re not just dispatching on groups of types (which the type system is already capable of via Union), they are assigning “types” to existing types in another dimension, think multiple subtyping!
In fact, in an all-powerful trait system, I would say types and traits are pretty much the same thing (see this reimagination of Julia in such a system).

3 Likes

Right, I meant groups as in “arbitrary groupings”, which I was using to recover multiple inheritance functionality.

I agree the type system + multiple dispatch is powerful, but implementing the same code using traits seems more extensible (e.g., if you want to dispatch on something later on that doesn’t fit into the type hierarchy). I can understand having type-dispatch as the default if it was built from trait-dispatch (such that it’s possible to switch from type to trait dispatch), but I don’t believe this is how Julia implements type-dispatch.

Perhaps this is more of an “Internals” question.

Since the trait system is implemented in terms of the type system and multiple dispatch, I don’t see how it can be more general. It’s very convenient, though.

1 Like

:man_facepalming:t2: I don’t know how I could have forgotten that. Thanks, that makes it feel internally consistent now.

So it sounds like traits are more general and extensible then multiple dispatch, but multiple dispatch is good enough for most situations.

1 Like

IMHO thinking about traits and multiple dispatch as separate things will not be helpful to you in the long run.

Traits in julia are just a consequence of how multiple dispatch works. They’re just using the fact that we can define “trait” methods for any type, and this method can be used expand the type signature of another method (usually by 1 type parameter) to get more detail to dispatch on.

Most of the time I use traits they are only “Holy Traits” in the purest sense in a subset of their uses. In other cases they are returning a user-defined field of the object, not a predefined trait for the object.

Holy trait:

trait(::Object1) = HasTrait()

Same method, not really a Holy trait:

trait(obj::Object2) = obj.trait

But we can use the trait in some other method, for both cases:

othermethod(obj) = othermethod(trait(obj), obj)
othermethod(::HastTrait, obj) = ...
othermethod(::NoTrait, obj) = ...
othermethod(::SomethingElse, obj) = ...

Being too focused on what a trait is can get in the way of writing the simplest possible code for your context. As a corollary, using SimpleTraits.jl or other packages that obscures the type system behind a fixed “Trait” concept can have the same effect.

2 Likes

It’s more correct to say traits are more general than type-based dispatch. Multiple dispatch extends single dispatch and solves the expression problem in the function domain, while traits build on the type hierarchy and solve after-the-fact multiple subtyping in the type domain, so they’re tackling different issues.
Note that the real cleverness of Holy Traits is that you do not need to alter the existing type hierarchy at all, so type-based dispatch codes are already extensible and can always be extended to traits without being written with traits in mind. Base Julia already uses traits, but we’d probably have to wait until 2.0 to have syntactic sugar/ formal definition of traits. The logic of a full-fledged trait system is harder to get right than you think: it took monumental efforts to get multiple dispatch to where it is (here’s a rabbit hole into the complexity of Julia’s subtyping), simply adding two separate trait dimensions Size and Odor as detailed in Traitor.jl’s example can already introduce quite the headache, let alone an infinitely extensible trait system. Do give it a try, you might win a beer from Tim Holy himself!

1 Like

I wonder if that is necessary after all. It may be one of those features that people initially design for, but in practice everyone seems to be doing fine without it.

1 Like