# Trait definitions: type level vs instance level

**URL:** <https://discourse.julialang.org/t/trait-definitions-type-level-vs-instance-level/103587>\
**Category:** General Usage\
**Created:** [September 6, 2023, 9:16pm UTC](https://discourse.julialang.org/t/trait-definitions-type-level-vs-instance-level/103587 "2023-09-06T21:16:03Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![joaquinpelle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joaquinpelle/32/49163_2.png) [@joaquinpelle](https://discourse.julialang.org/u/joaquinpelle)\
**Post date:** [September 6, 2023, 9:16pm UTC](https://discourse.julialang.org/t/trait-definitions-type-level-vs-instance-level/103587/1 "2023-09-06T21:16:03Z")

</div>

Hi there,

I’ve been thinking about trait-based dispatch, particularly from [this book excerpt](https://ahsmart.com/pub/holy-traits-design-patterns-and-best-practice-book/). The example there defines traits at the type level as follows

```julia
# Default behavior is illiquid
LiquidityStyle(::Type) = IsIlliquid()
# Cash is always liquid
LiquidityStyle(::Type{<:Cash}) = IsLiquid()
# Any subtype of Investment is liquid
LiquidityStyle(::Type{<:Investment}) = IsLiquid()

tradable(x::T) where {T} = tradable(LiquidityStyle(T), x)
tradable(::IsLiquid, x) = true
tradable(::IsIlliquid, x) = false

```

This made me wonder why we don’t define the traits at the instance level, like this

```julia
# Default behavior is illiquid
LiquidityStyle(::Any) = IsIlliquid()
# Cash is always liquid
LiquidityStyle(::Cash) = IsLiquid()
# Any subtype of Investment is liquid
LiquidityStyle(::Investment) = IsLiquid()

tradable(x) = tradable(LiquidityStyle(x), x)
tradable(::IsLiquid, x) = true
tradable(::IsIlliquid, x) = false

```

From what I understand, both approaches are type-stable and would compile to efficient code. Is this more a matter of common practice within the community, or are there advantages to defining traits at the type level?

Thanks in advance for your insights!

---

<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:** [September 6, 2023, 9:58pm UTC](https://discourse.julialang.org/t/trait-definitions-type-level-vs-instance-level/103587/2 "2023-09-06T21:58:42Z")

</div>

I don’t know for sure, but my guess is that you can always get a type from an instance with `typeof` but it could be potentially quite expensive to get an instance from a type.

This also allows generated functions that only know about the types of the inputs know about the traits too.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [September 7, 2023, 12:55pm UTC](https://discourse.julialang.org/t/trait-definitions-type-level-vs-instance-level/103587/3 "2023-09-07T12:55:31Z")

</div>

I think this is a matter of taste. Related thread:

> [@Should I dispatch on singleton types or their instances?](https://discourse.julialang.org/t/should-i-dispatch-on-singleton-types-or-their-instances/28204):
>
> When I am developing packages, I often come across the following problem. struct Step{T} end func(::Step{1}, x) = do something... func(::Step{2}, x) = do some different things... I am not sure should I use singleton types Type{Step{1}} and Type{Step{2}}, instead of singletons Step{1} and Step{2} to dispatch behaviors? func(::Type{Step{1}}, x) = do something... func(::Type{Step{2}}, x) = do some different things... Julia’s doc says [instances should be used](https://docs.julialang.org/en/v1/manual/types/#%22Value-types%22-1), is it a general rule or just for V…

> [@mrufsvold](#):
>
> my guess is that you can always get a type from an instance with `typeof` but it could be potentially quite expensive to get an instance from a type

Getting the instance of a singleton type is free. `T.instance` currently works, though it’s not documented or part of the public interface as far as I know. A guaranteed-to-work way is by relying on [incomplete](https://docs.julialang.org/en/v1/manual/constructors/#Incomplete-Initialization) initialization:

```julia
struct Helper{T}
  v::T
  Helper{T}() where {T} = new{T}()
end
uninitialized_instance(::Type{T}) where {T} = Helper{T}().v

```

```julia-repl
julia> uninitialized_instance(Nothing) == nothing
true

```

This is a bit roundabout, but could be abstracted into a tiny package easily. I actually wanted to [register](https://github.com/JuliaRegistries/General/pull/76530) such a package, but then gave up because the functionality seemed overly trivial so I wasn’t sure that anyone would use the package.

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [September 7, 2023, 1:30pm UTC](https://discourse.julialang.org/t/trait-definitions-type-level-vs-instance-level/103587/4 "2023-09-07T13:30:29Z")

</div>

Its not a matter of taste, there are clear reasons to define traits on types or on objects.

_Traits on types_  
We often want traits to work inside generated function (where we only have types), or on the eltype of an empty vector. Our target object we want to know the trait of is rarely a singleton - its the trait itself that is a singleton. So `T.instance` is not useful (its also touching internals). So we need to have a trait on the type.

_Traits on objects_  
We cant define the trait on the type if we only know the trait at run time. So the type is useless. For example, we need this in GeoInterface.jl because some objects, like vector points or gdal objects pased in from C, can be 2d or 3d depending on runtime size, So `is3d` has to work on objects rather than types. Nearly always its a compile-time operation (e.g. for a Tuple or GeometryBasics.jl geometry), but having it work on the object means the interface still works when its not, its just slower.
