# 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:** 20\
**Page:** 2

<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, 5:30am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/21 "2023-02-03T05:30:12Z")

</div>

> [@Mason](#):
>
> Many people ‘feel’ these limitations but don’t necessarily have a precise understanding or way of phrasing them.

Thank you, this is exactly the boat I was in until you elucidated it.

> [@Cloves\_Almeida](#):
>
> are traits that useful?

The idea presented here does feel a bit like playing with fire, so it’s definitely worth asking whether the benefits justify the danger.

Note, that a proper cost/benefit analysis cannot be made unless we have a good idea how well we can control the flames.

---

<div class="post-metadata">

**Author:** ![Steven\_Sagaert](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/steven_sagaert/32/29578_2.png) [@Steven\_Sagaert](https://discourse.julialang.org/u/Steven_Sagaert)\
**Post date:** [February 3, 2023, 10:06am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/22 "2023-02-03T10:06:44Z")

</div>

I think correctly specifying via the types of functions in packages what capabilities (like generic indexing or 1-based indexing) are supported is that killer application. See also the discussion in the thread about 0-based indexing being a poison pill.

I find traits in Scala pretty useful.

---

<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, 7:43pm UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/23 "2023-02-03T19:43:31Z")

</div>

> [@Steven\_Sagaert](#):
>
> generic indexing or 1-based indexing

I actually don’t think traits are the right solution for this one. I think this is better addressed by instead making it easier to use 1-based indices to access containers without 1-based indices, so that 1-based indexing can be considered “generic” again.

One idea is to make a _1-based index type_, as illustrated by the [OrdinalIndexing.jl](https://github.com/jishnub/OrdinalIndexing.jl) package: e.g., `v[1st]`, `v[2nd]`, `v[(1:5)th]`. Such indices offer a 1-based indexing assurance.

Another idea is to make a _1-based indexing view_, as @DNF ponders in [this post](https://discourse.julialang.org/t/is-offsetarrays-jl-a-poison-pill/85188/21): e.g. `OneBasedView(v)[1:5]`. This seems inconvenient, but to illustrate how it’s not so bad: you could write a function like this:

```julia
hermitian!(A) = let A = OneBasedView(A), (m, n) = size(A)
    m == n || error("Matrix must be square")
    @inbounds for i = 1:m, j = i:n
        A[i, j] = A[i, j] + A[j, i]'
        A[j, i] = A[i, j]'
    end
end

```

Notice that `OneBasedView` is called only once at the top, and the rest of the function accesses `A` through this view.

These possibilities are also pondered in [this thread](https://discourse.julialang.org/t/ordinal-indexing-as-a-language-feature/91970).

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [February 3, 2023, 7:47pm UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/24 "2023-02-03T19:47:00Z")

</div>

@uniment, if your post is indirectly asking the question, “mind if I try adding better traits to Julia?” then by all means go for it! No one wants to hold back a trait-enthusiast, I was just explaining the challenges.

Just keep in mind that there are good reasons for being conservative when it comes to merging big PRs that might add features we’d later regret—with Julia in stable release mode, there is no going back once a release has been made. Demonstrating tangible benefit from your work as early as possible will help convince a lot of people that big PRs should be merged.

---

<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 4, 2023, 12:10am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/25 "2023-02-04T00:10:44Z")

</div>

> [@tim.holy](#):
>
> No one wants to hold back a trait-enthusiast

I wouldn’t call myself that. I’m just an engineer, and when people complain, my animal instinct is to seek a technical solution 😅 I’ll let others debate whether traits are important enough to justify language support (I’m currently undecided).

My intent here is to add some heat to anneal the problem, to test whether we’re stuck in a local optimum or if we’ve found the best solution. What I’ve seen suggested that we hadn’t. An idea came to mind that appears to address the pain points, so I wanted to test it through dialog rather than putting in tremendous effort implementing it only to discover it was a poor idea from the outset.

> [@tim.holy](#):
>
> Demonstrating tangible benefit from your work as early as possible will help convince a lot of people that big PRs should be merged.

I appreciate that this approach is often preferable. However, for this idea, proper implementation as a package would take maybe ten times as much effort and yield an inferior and unpleasant result, so it’s a poor approach and winning the package popularity contest wouldn’t be a good litmus test. I’ll also take this opportunity to point out that approaches that are optimal for packages aren’t always optimal for language-level solutions. Brainstorm/debate/[rubber duck debug](https://en.m.wikipedia.org/wiki/Rubber_duck_debugging) seems more appropriate for now.

Speaking of which, through this rubber ducking I found something I legitimately dislike about this proposal. Take the idea of dispatching on a container’s element type:

```julia
foo(A::AbstractArray{E}) where {E<:ElType} = ...

```

Naturally we would wish to extend this idea, to be able to dispatch on the _traits_ of the element type. But this proposal offers no such mechanism. ☹

Maybe `::` could be used?

```julia
foo(A::AbstractArray{E::ETr} where {E<:ElType, ETr>:ElTraits} = ...

```

I can’t say I’m a fan of this syntax though.

Also, by this proposal we’d be able to express vararg traits like so:

```julia
bar(x::X::XTr...) = ...

```

but there’s currently no way to express this using `Vararg` or `NTuple`. It could be expressed as:

```julia
bar(x::Vararg{X::XTr})

```

Still, the syntax irks me. It doesn’t feel right.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [February 4, 2023, 6:43am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/26 "2023-02-04T06:43:14Z")

</div>

> [@uniment](#):
>
> Naturally we would wish to extend this idea, to be able to dispatch on the _traits_ of the element type. But this proposal offers no such mechanism. ☹

What I think you’re looking for is the sort-of dual to `Union` - instead of being a subtype of one of its parameters, you’d want to require `T` to be a subtype of ALL of its parameters (I don’t recall the proper type theoretic name, so I’ll just call this `Join` for now - think of it as a non-eager `typejoin`, just aggregating types without collapsing to `Any` or some `Union`). That is, in your `Platypus` example you’d write it like

```julia
abstract type HasDuckBill end
abstract type LaysEggs end

abstract type Mammal end

struct Platypus <: Join{HasDuckBill, LaysEggs, Mammal} end
struct CanadianGoose <: Join{HasDuckBill, LaysEggs, Bird} end

foo(arr::AbstractArray{T}) where {T <: Join{HasDuckBill, LaysEggs}} = "Platypus or Goose"

```

This can of course lead to lots of ambiguity errors, e.g. if we just add the innocent looking

```julia
foo(arr::AbstractArray{T}) where {T <: Mammal} = "Not a bird!"

```

then

```julia
foo(Platypus[])

```

is ambiguous. This particular approach still has the disadvantage of not being able to easily add new traits to existing objects, since it keeps the type system of julia nominal (see [the docs](https://docs.julialang.org/en/v1/manual/types/) and [wikipedia](https://en.wikipedia.org/wiki/Nominal_type_system)). This is in principle fixable - just don’t require `Join` to be declared a supertype, i.e. don’t make it a nominal part of the type system - but I haven’t thought about the consequences of that too deeply. It’s so far the best I’ve come up with to introduce traits & multiple abstract subtyping to julia though 🤷 I quite like it, but solving the issues with it I haven’t found yet and actually implementing that is probably worth a master level thesis (who knows, maybe I’ll come back to this approach when I go for a masters…)

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [February 4, 2023, 6:56am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/27 "2023-02-04T06:56:13Z")

</div>

Are you thinking of

> **[Intersection type](https://en.wikipedia.org/wiki/Intersection_type)**
>
> Pages for logged out editors learn more
> 
> In type theory, an intersection type can be allocated to values that can be assigned both the type 
>   
>     
>       
> σ
>       
>     
> {\\displaystyle \\sigma }
>   
> and the type 
>   
>     
>       
> τ
>       
>     
> {\\displaystyle \\tau }
>   
> . This value can be given the intersection type 
>   
>     
>       
> σ
> ∩
> τ
>       
>     
> {\\displaystyle \\sigma \\cap \\tau }
>   
> in an intersection type system.
> Generally, if the rang...

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [February 4, 2023, 7:07am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/28 "2023-02-04T07:07:56Z")

</div>

That looks about right, yes! Good to know that built-up intuition about where our type system has holes leads to existing theory 😃 Now integrating that theory (and, if possible, in a backwards compatible way) is the hard part… I still like to think `Join` is a good name for this, since in principle `Join{Int, Float16} <: typejoin(Int, Float16)` should hold as well, I think.

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [February 4, 2023, 7:22am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/29 "2023-02-04T07:22:13Z")

</div>

I usually think of “join” as corresponding to union and “meet” as corresponding to intersection. Julia’s `typejoin` may be using “join” in some other sense I’m not familiar with.

> **[Join and meet](https://en.wikipedia.org/wiki/Join_and_meet)**
>
> All definitions tacitly require the homogeneous relation 
>   
>     
>       
> R
>       
>     
> {\\displaystyle R}
>   
> be transitive: for all 
>   
>     
>       
> a
> ,
> b
> ,
> c
> ,
>       
>     
> {\\displaystyle a,b,c,}
>   
> if 
>   
>     
>       
> a
> R
> b
>       
>     
> {\\displaystyle aRb}
>   
> and 
>   
>     
>       
> b
> R
> c
>       
>     
> {\\displaystyle bRc}
>   
> then 
>   
>     
>       
> a
> R
> c
> ...

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [February 4, 2023, 7:23am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/30 "2023-02-04T07:23:59Z")

</div>

Yes, that is the usual terminology and I agree that calling it `Join` is confusing in that sense - it wasn’t meant as a serious suggestion as a name either way, `Meet` is just as good (and delightfully concise) 🙂 The name is yak shaveable.

---

<div class="post-metadata">

**Author:** ![mrufsvold](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mrufsvold/32/31600_2.png) [@mrufsvold](https://discourse.julialang.org/u/mrufsvold)\
**Post date:** [February 4, 2023, 3:56pm UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/32 "2023-02-04T15:56:50Z")

</div>

I’ve been chewing on this for a minute and woke up this morning with this exact thought! Glad I’m not crazy 😁

I think that this captures essence of what people sometimes clumsily complain about Julia’s type system – “I want to inherit from two types.”

You don’t need a separate construct for traits, I think. Just a smart way to pass on behavior from multiple supertypes.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [February 4, 2023, 7:22pm UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/33 "2023-02-04T19:22:15Z")

</div>

These are essentially like interfaces in Java. Our abstract types are essentially the same as [Java interfaces](https://docs.oracle.com/javase/tutorial/java/IandI/createinterface.html#:~:text=Interfaces%20in%20Java,default%20methods%20and%20static%20methods.).

In your case though, does order of the multiple subtyping matter?

---

<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 5, 2023, 8:18am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/34 "2023-02-05T08:18:11Z")

</div>

> [@Sukera](#):
>
> What I think you’re looking for is the sort-of dual to `Union`

I think you’re right. I’m trying to express a position in an object type hierarchy _and_ the possession of a set of traits; the solution to my dissatisfaction is to find a more concise way to express this, and the most concise expression is as an intersection—a construct that Julia simply doesn’t have.

> [@](#):
>
> Now integrating that theory (and, if possible, in a backwards compatible way) is the hard part

I think it could be backwards-compatible. Simply put, instead of `x::X` asserting `typeof(x)<:X`, and instead of having methods dispatch on `typeof`, imagine if we instead had `x::X` assert `traitsof(x)<:X` and methods dispatch on `traitsof`.

Maybe it could work like this:

* * *

## _Intersecting Types and Traits_

First, introduce a new type. You called it `Meet`, but to keep the spirit of shaven yaks alive I’ll call it `Intersection`. In a tree-shaped type hierarchy, the notion of an intersection is useless; `Intersection{Signed, Number}` is `Signed`, and `Intersection{String, Int}` is `Union{}`. With such a type system, there’s simply no way that `Intersection` could be useful (hence we don’t have it). But let’s introduce it.

Next, introduce the notion of _multiple hierarchies_. Currently we have only one: the hierarchy of _objects_ with a top of `Any` and a bottom of `Union{}`, and all types declared in all modules share this hierarchy. A trait such as `Growable` doesn’t belong on this hierarchy—growability isn’t a category of _thing_; it’s a property that belongs on its own plane of existence.

To express this, maybe we could have a “type module” to declare a completely separate hierarchy:

```julia
type module Growable
    struct Is end
    struct IsNot end
end
Growable.Is <: Growable.Any # true
Main.Any != Growable.Any # true
Intersection{Growable.Is, Growable.IsNot} # Growable.Union{}

```

With orthogonality assumed, the intersection of a type in the `Growable` hierarchy with a type in the object hierarchy will be irreducible—i.e., `Intersection{AbstractString, Growable.Is}` cannot be reduced to `Union{}`, but instead must remain `Intersection{AbstractString, Growable.Is}`. In fact, every trait should exist in its own hierarchy, ideally orthogonal to every other trait, so that intersections with multiple traits can be formed.

Let’s finalize the specification of `Intersection`. Like `Union` and `Tuple`, `Intersection` types are covariant in their parameters, so that if `T1<:T2` and `MyTrait.TA<:MyTrait.TB`, then `Intersection{T1, MyTrait.TA} <: Intersection{T2, MyTrait.TB}`. Also, like `Union`, order doesn’t matter.

Next, imagine if we have these definitions:

```julia
traitsof(x) = Intersection{typeof(x), traits(typeof(x))}
traits(::Type{T}) where T = Any 

```

As before, `traits` could be a special function, in that it dispatches on its argument’s `typeof` instead of `traitsof`.

For a type that doesn’t have any traits, you can see how `traitsof(x)` reduces to `typeof(x)`, and therefore `x::X` is backward-compatible. And if `x` now becomes traitful, but `X` is still just a supertype, `x isa X` remains true and `x::X` still asserts correctly.

Then, to add traits to a type, we can perform a trick similar to the OP:

```julia
add_traits(T, Tr) = let Trs = Intersection{traits(T), Tr}
    eval(:( traits(::Type{var"#T"}) where var"#T"<:$T = $Trs ))
end

```

I think we have all the pieces we need now. With this, function dispatch could work like this:

```julia
# function call
f(a, b, c, d)

# method signature
f(::Any, ::B, ::CTrait.Tr, ::Intersection{D, DTrait.Tr})

# this method is chosen if:
traitsof(a) <: Any &&
traitsof(b) <: B && 
traitsof(c) <: CTrait.Tr &&
traitsof(d) <: Intersection{D, DTrait.Tr}

# (and, of course, if this is the most specific method 
# that satisfies these constraints.)

```

Here’s a question: what should `typeof(Growable.Is)` be? Should `Growable.Is isa Type`? If so, then maybe we can make this shorter with `∩(A::Type, B::Type) = Intersection{A, B}`.

Are there any concerns with this? Would it work? Did I miss something? Can I choose better function names?

* * *

## _Taking a stab at method ambiguities_

One thing I’ve noticed about method ambiguities is that anecdotally it seems like they don’t need to exist. Namely, ambiguities are introduced and resolved, but if methods were written in a different order (i.e., _starting_ with the method that solved the ambiguity) the ambiguity would never have existed.

Maybe we can leverage Julia’s dynamic nature—which gets in the way sometimes by forcing method definitions to be sprinkled around type declarations (and vice versa) because of the linear evaluation order—and put it to use here. Namely, every time a method is declared, we could check if it creates any ambiguities with existing methods and throw an error if it does. That would alleviate concerns over method ambiguities by making them strictly impossible to begin with.

Is it generally true that ambiguities can be avoided throughout the course of declaring a function’s methods by properly ordering evaluation? Or am I wrong—would throwing errors on ambiguous method declarations cause some valid method signatures to be unreachable?

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [February 5, 2023, 1:43pm UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/35 "2023-02-05T13:43:57Z")

</div>

> [@uniment](#):
>
> I think you’re right. I’m trying to express a position in an object type hierarchy _and_ the possession of a set of traits;

That’s the same thing. In julia (& with multiple dispatch) , while we declare subtype relations explicitly, in practice that is only really true if we can really use the subtype in every place where we’d expect (at most) the supertype. I.e., given a `T <: S` and a function `foo(::S)`, the declared relationship `T <: S` doesn’t really matter if `foo(::S)` then doesn’t succeed (while it’s of course nice to declare your intent, right now there is no explicit layer in the compiler checking for that & warning you ahead of time, aside from JET.jl). You can say that the type `S` “possesses” the trait “Passable as the sole argument to `foo`” (which is really all that’s needed). In general, this definition of subtyping is called [the Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). From this, it follows naturally that declaring `T <: Meet{S, R}` means "`T` MUST be able to be used in any place that we’d expect either `S` or `R` (or get an ambiguity error, if e.g. both `foo(::S)` and `foo(::R)` but no `foo(::T)` exist).

Trivially, if we have `T <: S` then `Meet{T, S}` simplifies just to `T` for all `S` for which `T <: S` holds. `Meet{Union{}, T}` always simplifies to `Union{}`, because `Union{} <: T` is always true. Additionally, `Union{Meet{}, T}` becomes just `T` again. (Try and plug in various types of the chain `Union{} <: Int <: Integer <: Any` and see what you get - in your head with `Meet` and in the REPL with `Union`).

> [@uniment](#):
>
> In a tree-shaped type hierarchy, the notion of an intersection is useless;

Julia does not have a tree shaped hierarchy - it’s a [partially ordered lattice](https://en.wikipedia.org/wiki/Lattice_(order)), with `typejoin` being the `join` operation and `typeintersect` being the `meet` operation. Thus, `Any` is the Top of that lattice (there are no “greater” elements in the partial order) and `Union{}` is the Bottom of that lattice (there are no “smaller” elements in the partial order).

That’s where the tricky cases come in - what about `typejoin(Meet{}, Any)`? Or `typeintersect(Meet{}, Union{})`? Should they return `Any` and `Union{}` respectively, delegating `Meet` to just an intermediary? I think so, but that’s part of what I haven’t worked out yet. Other parts are what should happen with `typejoin(Meet{S, T}, Meet{T, R})` or `typeintersect(Meet{S, T}, Meet{R, Q})` (I suspect it would be `T` and `Union{}` respectively - haven’t worked it out yet).

Similarly, since we can’t subtype concrete types, it doesn’t really make sense to form a `Meet{Int, T}` - that then ought to be disallowed as well, only allowing `Meet` to be used with abstract types in its parameters.

Having arrived at this, we can view `Meet` in a different light - allowing multiple abstract inheritance of julia **behavior** , because abstract types _are already our interface definitions_ (possibly extended by concrete structs - concrete structs that directly subtype `Any` are a bit of a PITA, but luckily strengthening the guarantees of the direct super type is not a breaking change generally speaking).

> [@uniment](#):
>
> Next, introduce the notion of _multiple hierarchies_.

This is not necessary and introduces unnecessary complexity in the type lattice. Again, there is no hierarchy - it’s a partially ordered lattice. What you’re proposing would mean having multiple type lattices (i.e. multiple type _systems_) in one language, effectively splitting the type system in two. Now you have a meta-problem - partially ordering those lattices and resolving conflicts & ambiguities between them.

> [@uniment](#):
>
> Is it generally true that ambiguities can be avoided throughout the course of declaring a function’s methods by properly ordering evaluation?

Yes, but that would mean requiring julia to typecheck function signatures ahead of time for all possible calls, which is undesirable in a dynamic language (in fact, that’s how statically typed languages work - you get a compiletime error when there’s a call that is ambiguous, instead of a runtime ambiguity error. The method may have been added in a language with global `eval`, after all).

---

<div class="post-metadata">

**Author:** ![ParadaCarleton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/paradacarleton/32/20005_2.png) [@ParadaCarleton](https://discourse.julialang.org/u/ParadaCarleton)\
**Post date:** [February 5, 2023, 11:18pm UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/36 "2023-02-05T23:18:00Z")

</div>

> [@tim.holy](#):
>
> - implementing a nice trait system and then making Julia exploit it would likely be a multi-month job, and

Definitely, but I think implementing a nice trait system first would be substantially easier. Rewriting Base to take advantage of it _everywhere_ would definitely be breaking and would also be a _lot_ harder. I’d save it for whenever there’s more resources and a desire for a 2.0 release.

---

<div class="post-metadata">

**Author:** ![ParadaCarleton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/paradacarleton/32/20005_2.png) [@ParadaCarleton](https://discourse.julialang.org/u/ParadaCarleton)\
**Post date:** [February 5, 2023, 11:20pm UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/37 "2023-02-05T23:20:54Z")

</div>

I’d also like to mention my current favorite implementation of traits–[WhereTraits.jl](https://github.com/jolin-io/WhereTraits.jl).

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [February 5, 2023, 11:46pm UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/38 "2023-02-05T23:46:18Z")

</div>

> Definitely, but I think implementing a nice trait system first would be substantially easier. Rewriting Base to take advantage of it _everywhere_ would definitely be breaking and would also be a _lot_ harder.

That, however, may be where the bar is now. (Not the breaking part—the art would be doing what can be done without being breaking—but in terms of “a lot of work”.) Now that we’re in the 1.x stable release series, there’s well-justified reluctance to substantial new features without there being a “consumer”: how else do you know whether you’ve gotten the design right, and that the change yields real benefits without causing problems elsewhere? Surely you’ve noticed this in the discussion of [https://github.com/JuliaLang/julia/pull/24990](https://github.com/JuliaLang/julia/pull/24990)? This is kind of a more refined version of [Julia is not at that stage of development anymore](https://discourse.julialang.org/t/psa-julia-is-not-at-that-stage-of-development-anymore/44872)–deep changes are now harder. That’s the price of Julia’s success!

---

<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 6, 2023, 12:36am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/39 "2023-02-06T00:36:26Z")

</div>

I don’t understand WhereTraits.jl enough to know what’s going on here. Is this performance a fundamental architectural limitation, or is it just an artifact of its current state of implementation?

```julia
julia> using WhereTraits
       @traits f(a) where {isodd(a)} = (a+1)/2
       @traits f(a) where {!isodd(a)} = a/2

julia> @btime f(2)
       @btime f(3)
  25.911 ns (0 allocations: 0 bytes)
  25.402 ns (0 allocations: 0 bytes)
2.0

```

For comparison:

```julia
julia> g(a) = isodd(a) ? (a+1)/2 : a/2
       @btime g(2)
       @btime g(3)
  2.300 ns (0 allocations: 0 bytes)
  2.300 ns (0 allocations: 0 bytes)
2.0

```

I get the appeal of wanting to dispatch on arbitrary predicates, but I’m not yet sure if it’s worth it. Totally open to being talked into it though.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [February 6, 2023, 1:26am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/40 "2023-02-06T01:26:26Z")

</div>

Hmm, at a glance that looks a bit similar to what [refinement types](https://en.wikipedia.org/wiki/Refinement_type) would look like (something I think might be a nice fit with Julia).

#### Elaboration

Refinement types would allow for something like:

```julia
function somescore(val::Int) where {0 <= val <= 60 && iseven(val)}
  # stuff
end

function getval(list::Vector, index::Int) where {firstindex(list) <= index <= lastindex(list)}
  # stuff
end

```

What’s nice about a refinement type system is that it can be checked that a function will respect its contracts _at compile time_. I’d imagine that this functionality would go quite some way to helping ensure the correctness of programs too.

For further reading, I’d recommend giving [https://arxiv.org/pdf/2010.07763.pdf](https://arxiv.org/pdf/2010.07763.pdf) a look.

Racket, Ruby, Scala, and Haskell have all had refinement types added on top, which gives me hope that this actually could be viable for Julia.

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [February 6, 2023, 1:31am UTC](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914/41 "2023-02-06T01:31:48Z")

</div>

> [@uniment](#):
>
> I don’t understand WhereTraits.jl enough to know what’s going on here. Is this performance a fundamental architectural limitation, or is it just an artifact of its current state of implementation?

@schlichtanders would know best.

[Previous page](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914.md?page=1)

[Next page](https://discourse.julialang.org/t/rfc-language-support-for-traits-yay-or-nay/93914.md?page=3)
