# Dispatch on symbol name for \`getproperty\`

**URL:** <https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470>\
**Category:** Internals & Design\
**Tags:** question\
**Created:** [August 28, 2022, 8:19pm UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470 "2022-08-28T20:19:15Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![goerz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerz/32/3269_2.png) [@goerz](https://discourse.julialang.org/u/goerz)\
**Post date:** [August 28, 2022, 8:19pm UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/1 "2022-08-28T20:19:15Z")

</div>

The [documentation for `getproperty`](https://docs.julialang.org/en/v1/base/base/#Base.getproperty) has this example:

```julia
function Base.getproperty(obj::MyType, sym::Symbol)
    if sym === :special
        return obj.x + 1
    else # fallback to getfield
        return getfield(obj, sym)
    end
end

```

Is there a way to dispatch `getproperty` for a specific symbol? That is, something like

```julia
Base.getproperty(obj::MyType, sym::Val{:special}) = obj.x + 1

```

It seems strange having to go through a chain of if/else statements (if there are multiple properties) at run-time instead of at compile time.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [August 28, 2022, 9:20pm UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/2 "2022-08-28T21:20:18Z")

</div>

> [@goerz](#):
>
> It seems strange having to go through a chain of if/else statements

I’m pretty sure it doesn’t, and that constant propagation handles this.

If you define

```julia
foo(m::MyType) = m.special

```

and call `@code_llvm foo(m)` you can see there are no branches.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [August 28, 2022, 10:32pm UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/3 "2022-08-28T22:32:58Z")

</div>

You can do something like this:

```julia
getproperty(obj::MyType, s::Symbol) = getproperty(obj, Val(s))
getproperty(obj::MyType, Val{:special}) = obj.x + 1

```

but you need to redefine `getproperty` for all symbols you want to be part of the interface.

The `if ... else` does create run time dispatch, allocations, etc. I have just been there last week.

An alternative is to put `special` as a type parameter of `obj`, to dispatch with:

```julia
function Base.getproperty(obj::MyType{Special}, sym::Symbol) where Special
    if sym === Special
        return obj.x + 1
    else # fallback to getfield
        return getfield(obj, sym)
    end
end

```

That eliminates the branch at compile time (but the signature of your object must be `MyObj{:Special}` or similar, with all the constructors adapted accordingly, which may not be more practical than redefine `getproperty` for all fields as the first alternative).

---

<div class="post-metadata">

**Author:** ![goerz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerz/32/3269_2.png) [@goerz](https://discourse.julialang.org/u/goerz)\
**Post date:** [August 29, 2022, 2:55am UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/4 "2022-08-29T02:55:29Z")

</div>

I like it!

You could still define a general fallback, even with `Val`, with something like this:

```julia
getproperty(obj::MyType, name::Val) = getfield(obj, typeof(name).parameters[1])

```

That’s with `typeof(name).parameters[1]` being the only way I’ve figured out to convert e.g. `Val(:x)` back to the symbol `:x`

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [August 29, 2022, 4:07am UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/5 "2022-08-29T04:07:21Z")

</div>

> [@lmiq](#):
>
> ```julia
> getproperty(obj::MyType, s::Symbol) = getproperty(obj, Val(s))
> getproperty(obj::MyType, ::Val{:special}) = obj.x + 1
> 
> ```

This fails at `obj.x` because `getproperty` lacks a fallback branch, and there’s no method for `Val{:x}`. Adding goerz’s fallback works, though there is a way to do it without digging into internal fields:

```julia
getValue(::Val{T}) where T = T
getproperty(obj::MyType, name::Val) = getfield(obj, getValue(name))

# or if you don't need to decouple the two,
getproperty(obj::MyType, name::Val{T}) where T = getfield(obj, T)

```

I wouldn’t name it `getproperty` though, `m.special` is always going to lower to `getproperty(m, ::Symbol)`, so dispatching on `Val` really should be done by a separate internal function.

> [@lmiq](#):
>
> `= getproperty(obj, Val(s))`

More to the point, this part is inherently type-unstable, going from 1 `Symbol` type to many `Val` types based on the runtime `Symbol` instance. To get rid of that instability, you really need to bake information into the argument types, whether you dispatch on instances of `Val{:special}`, `MyType{:special}`, or a special singleton `struct MySpecial end`.

Getting rid of that instability isn’t often necessary. As DNF pointed out, the compiler does constant propagation, and it will do it with partial information for accessing struct fields or Tuple elements. Given `foo(m::MyType) = m.special`, the call `foo(m)` compiles `m.special` without branches even though `m` is not a constant.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [August 29, 2022, 4:24am UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/6 "2022-08-29T04:24:02Z")

</div>

> [@Benny](#):
>
> This fails at `obj.x` because `getproperty` lacks a fallback branch

That’s why I said to implement all the `getindex(obj::MyType, ::Val{:x})` etc, required by the API. I was not aware of the getValue possibility (will try it).

Edit: one needs to call `getfield` then: `getproperty(obj::MyObj, ::Val{:x}) = getfield(obj, :x)`.

> [@Benny](#):
>
> More to the point, this part is inherently type-unstable, going from 1 `Symbol` type to many `Val` types based on the runtime `Symbol` instance.

When a function uses `obj.special`, hardcoded, the compiler is eliminating the instability and just using the final call here.

> [@Benny](#):
>
> ith partial information for accessing struct fields or Tuple elements. Given `foo(m::MyType) = m.special`, the call `foo(m)` compiles `m.special` without branches even though `m` is not a constant.

Well, it doesn’t always with the branched version. I have pulled my hair off in a real case where it failed to do that. And finding where was the problem was a pretty hard, btw, only with Chutchlu I was able to find the issue.

Maybe that is dependent on the number of properties.

Edit2: my case was somewhat more complicated, because the special name was a variable to be set on the construction of the object, thus I had to make it a type parameter anyway.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [August 29, 2022, 4:41am UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/7 "2022-08-29T04:41:34Z")

</div>

> [@lmiq](#):
>
> Well, it doesn’t always with the branched version. I have pulled my hair off in a real case where it failed to do that.

Maybe it’s dependent on how big the branched method is? I’m not positive but I think branch elimination only works when the code is all in one place, so the branching method has to be inlined into the caller method that has the constant information for eliminating branches. I know that smaller methods like OP’s example are more easily inlined.

In that case, maybe your refactoring does help by splitting 1 big method with many branches into many smaller `::Val` methods. Just propagating a constant `s` to an inlined `getproperty(obj::MyType, s::Symbol) = getproperty(obj, Val(s))` would be enough to select the `Val` method at compile-time, and a small enough `Val` method itself could be inlined. Not sure how to test this hypothesis though. Testing the method `foo(m::MyType) = m.special` along with the other code written so far would work, but problem is how exactly to edit the branched `getproperty` in the OP to be un-inline-able. I don’t know how the compiler makes that decision: many branches, big branches, how big?

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [August 29, 2022, 4:58am UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/8 "2022-08-29T04:58:43Z")

</div>

> [@lmiq](#):
>
> The `if ... else` does create run time dispatch, allocations, etc. I have just been there last week.

Can you show an example? My, admittedly brief, test showed no such thing, and I got llvm code identical to a normal `getfield`, with no branches and zero allocations. As long as the `Symbol` argument is literal, and isn’t decided at runtime.

Edit: ok, so it’s a bit variable/unpredictable? But the example in the OP seems to constant fold fine.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [August 29, 2022, 10:42am UTC](https://discourse.julialang.org/t/dispatch-on-symbol-name-for-getproperty/86470/9 "2022-08-29T10:42:42Z")

</div>

> [@DNF](#):
>
> Can you show an example?

My actual case contained quite complicated structures, containing another nested custom relatively complicated structures. I tested now here with this:

```julia
julia> struct S5
           a::Int
           b::Float32
           c::Float64
           d::Char
           e::Bool
       end

```

and it constant-folds well. Thus, I’m not sure if I can come up with a simple MWE. (it is also always possible that in the process of fixing my code I fixed this and something else at the same time… If at some point I end up disentangling that I will let you know).

@DNF : I think you are probably right (as usual), and what fixed my problem was the type-parameter for the variable struct name, and nothing else. Though I would like to see some guarantee of that behavior, because _finding_ that issue inside a package is quite hard, because the allocating manifests in very unpredictable places.
