# RFC: Language Support for Traits — Yay or Nay?

**URL:** https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914
**Category:** Internals & Design
**Tags:** question, proposal, inheritance, traits, interface
**Created:** [February 2, 2023, 11:10am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914 "2023-02-02T11:10:06Z")
**Posts on this page:** 1
**Showing post:** 17

<div class="post-metadata">

### Author: ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)
#### Post date: [February 3, 2023, 12:33am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/17 "2023-02-03T00:33:28Z")

</div>

Oops, for some reason I deleted this from my OP: I think also `traits` might have to be a special function, in that `traits` is not called to dispatch on the traits of its argument (to avoid stackoverflow).

> [@Jean\_Michel](#):
>
> Why not

It could be similar to that, or new keywords could be reserved; I just wanted to express the idea in terms of existing Julia as much as possible for clarity.

> [@tim.holy](#):
>
> if someone wants to make this happen, the best place is to start with the package ecosystem

To make this idea “work” as a package—that is to say, to hack the idea so that on a surface syntax level it works with the current dispatch system—I’d make a macro which would wrap function arguments such that whenever calling a function,

```julia
f(a, b, c, d)
# is transformed to
f(TTr(a), TTr(b), TTr(c), TTr(d))

```

where `TTr` is defined as:

```julia
struct TTr{X, XTr}  
    x::X
    TTr(x::X) where X = new{X, traits(X)}(x)  
end

```

Then, method signatures would be transformed like this:

```julia
f(a, b::B, c::::CTr, d::D::DTr) = ...
# is transformed to
f(a::TTr{<:Any, >:Union{}}, b::TTr{<:B, >:Union{}}, c::TTr{<:Any, >:CTr}, d::TTr{<:D, >:DTr}) = ...

```

It should work quite handsomely with methods defined and called like this.

However, you’re confronted with the fact that this would only work for functions that have already been declared this way; nothing in `Base` and no other package would be compatible with this function call, so you’d have to know at the callsite not to use this macro on those. It would be an un-composable package, an island of its own. Unless…

> [@tim.holy](#):
>
> you’d create a _package_ called `BaseRewrite` that overwrites lots of methods in Base to make them traitful. Then you’d create branches of a bunch of packages to depend on `BaseRewrite`

Yikes!

> [@tim.holy](#):
>
> it’s possible it would be a breaking change

I believe this proposal, implemented in the compiler, isn’t breaking (at least if we consider it okay to break code which uses the pattern `var::Type1::Type2`, which I’d argue is already broken). Code that already implements Holy traits would still work; it just wouldn’t be necessary to do it that way anymore. And code that doesn’t use any traits would work as-is.

Implementing it as a package, however, would require breaking literally everything and a herculean effort to make it work again.

> [@tim.holy](#):
>
> uncertainties about the impact widespread trait-usage would have in terms of ambiguities

Yes, this is the sort of insight I’m hoping to gain here. What are the most onerous and insidious possibilities you can imagine this proposal would open? Would they be infeasible to address through proper patterns and use of `Test.detect_ambiguities`? Would we need new tools to understand the interactions? Would such problems be solvable, or would it be a Pandora’s box?

> [@rveltz](#):
>
> would a trait be able to tackle

Possibly, but I feel like that problem is better solved by adding better support for wrapper structs. My ideas on that topic aren’t mature enough to be worth sharing though.

> [@klacru](#):
>
> the proposal to invert the meaning of `Union` when applied to traits is not acceptable (actually an intersection is meant).

This is exactly what inverting the meaning does: when you query whether an object implements a set of traits, increasing the size of the query set decreases the size of the set of objects that match the criterion, since the latter set is an intersection. An object that implements a very large set of traits is a special object, as the set of such objects is very small.

Being able to dispatch on both type hierarchy and traits allows for further intersection.

> [@Mason](#):
>
> E.g. if I define a trait `MyTrait`, there is no way I can define `Base.show(::IO, ::::::MyTrait)` without type piracy.

This is an excellent example of how this proposal would allow greater extensibility. This is probably also the best example of why this idea could open up unexpected dispatch ambiguities, because every object must `show` 😅 If this problem is resolvable, then perhaps any such problem would be.

For example, suppose I define `MyTrait2` and `show(::IO, ::::MyTrait2)`, and I have an object `::Foo` for which `traits(Foo) >: Union{MyTrait, MyTrait2}`. This creates an ambiguity for `::Foo`’s `show` method, unless we either define `show(::IO, ::::Union{MyTrait, MyTrait2})`, or we define `show(::IO, ::Foo)`.

Is it possible to develop a set of rules—language features, design patterns, best practices, etc.—by which such an ambiguity could readily either a) be anticipated and avoided, or b) be detected and resolved? Perhaps we’d want some friction in the process of adding traits to a type, because adding a new trait can suddenly introduce dispatch ambiguities? Or maybe we’d want type hierarchy visualization tools? Or maybe we’d want language features to enforce orthogonality of traits? What would trait orthogonality even mean? I’m spitballing here and wide open to ideas.

It seems like maybe traits should continue to be considered an “advanced” feature, a tool with sharp edges that neophytes would be discouraged from using unless there are ways to put safeties in place. I suspect that the extra `::` should also add friction to cause a preference toward dispatching on the type hierarchy instead of traits, but maybe I’m wrong.

> [@Zach\_Christensen](#):
>
> yet to see a proposal for formalizing traits that saves more than three lines of code

As @Mason points out, this isn’t just about saving three lines of code. Functions that don’t already dispatch on traits, cannot be extended to. And functions that do, can’t be extended to specialize on new trait categories (groupings of traits that are returned by a `GetThisTrait` function). By contrast, this proposal makes such trait categories unnecessary.

That curse is also a blessing: it forces trait dispatch to be rather specialized in what trait categories it can dispatch on, which alleviates the potential for ambiguities (the ambiguities for the `show` method example above are impossible with Holy traits). But it makes further specialization impossible.

> [@Zach\_Christensen](#):
>
> If you could somehow overcome type instabilities … then you’d be on to something

Can I interest you in type-parameterizing `Core.Box`? As shown [here](https://discourse.julialang.org/t/writing-a-static-function/92882/20), that could result in immediate performance improvements. I also have ideas for how to reduce the number of boxes that are generated, but perhaps that should be for a different post (infact, I already pondered a simple one [here](https://discourse.julialang.org/t/call-fastclosures-jl-closure-in-array-comprehensions/93622) that garnered zero attention, and [this one](https://julialang.slack.com/archives/CF53T1DU4/p1674982840657139?thread_ts=1674979071.912009&cid=CF53T1DU4) only became an argument. I’m also hoping to build support for [this one](https://github.com/JuliaLang/julia/issues/47760).).

---

_[View the full topic](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914)._
