# Mapping a 'getter'

**URL:** https://discourse.julialang.org/t/mapping-a-getter/107276
**Category:** New to Julia
**Created:** [December 7, 2023, 3:02pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276 "2023-12-07T15:02:14Z")
**Posts on this page:** 14
**Page:** 2

<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: [December 7, 2023, 8:50pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/21 "2023-12-07T20:50:23Z")

</div>

> [@Marco\_Antoniotti](#):
>
> It is idiomatic in at least another language. The one with even better macros than Julia (look ma, no ‘@’), many implementations and some very good compilers.

Could you name which one you had in mind, I can’t guess.

> [@DNF](#):
>
> the `@` naming convention was deliberately chosen to visually distinguish macros. They’re not a necessary evil, but a design choice.

The nice thing beyond just visual distinction is that a macro and function with shared purpose can share a name e.g. `@eval`/`eval`. I don’t think it’s as simple as choosing to require an `@` character, it’s fairly necessary because function calls and macro calls look very different and source code looks very different from `Expr`. I don’t know if anyone still considers Julia homoiconic but it’s definitely not on the level of Lisp.

---

<div class="post-metadata">

### Author: ![bertschi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bertschi/32/33462_2.png) [@bertschi](https://discourse.julialang.org/u/bertschi)
#### Post date: [December 7, 2023, 8:52pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/22 "2023-12-07T20:52:30Z")

</div>

Common Lisp?

---

<div class="post-metadata">

### Author: ![mike.ingold](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mike.ingold/32/203749_2.png) [@mike.ingold](https://discourse.julialang.org/u/mike.ingold)
#### Post date: [December 7, 2023, 9:10pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/23 "2023-12-07T21:10:56Z")

</div>

> [@Marco\_Antoniotti](#):
>
> `map(*age*, ps)`

I don’t have a Julia REPL handy and maybe I’m mis-remembering, but isn’t there a syntax feature that automatically interprets `_.age` as the equivalent of an anonymous function `p -> p.age`? If I remember correctly, it only currently works for accessing fields and indices (e.g. `_[1]`). This would enable the above to be written simply as `map(_.age, ps)`.

---

<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: [December 7, 2023, 9:15pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/24 "2023-12-07T21:15:14Z")

</div>

> [@mike.ingold](#):
>
> isn’t there a syntax feature that automatically interprets `_.age`

All-underscore variables don’t work in right-hand expressions, though it’s not an outright invalid expression e.g. `:(_[])` is valid while `:(for end)` is not. Macros can transform right-hand underscores, like Pipe.jl.

---

<div class="post-metadata">

### Author: ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)
#### Post date: [December 7, 2023, 9:18pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/25 "2023-12-07T21:18:13Z")

</div>

> [@DNF](#):
>
> But what’s wrong with `getfield.(persons, :age)`?

Type instability is the problem.

```julia-repl
julia> getfirst(z) = getfield.(z,:first)
getfirst (generic function with 1 method)

julia> z = [rand(1:10) => rand() for _ in 1:1000];

julia> using BenchmarkTools

julia> @btime getfirst($z);
  22.500 μs (1002 allocations: 39.20 KiB)

julia> @code_warntype getfirst(z)
MethodInstance for getfirst(::Vector{Pair{Int64, Float64}})
  from getfirst(z) @ Main REPL[207]:1
Arguments
  #self#::Core.Const(getfirst)
  z::Vector{Pair{Int64, Float64}}
Body::Union{Vector{Float64}, Vector{Int64}, Vector{Real}}
1 ─ %1 = Base.broadcasted(Main.getfield, z, :first)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(getfield), Tuple{Vector{Pair{Int64, Float64}}, Base.RefValue{Symbol}}}
│ %2 = Base.materialize(%1)::Union{Vector{Float64}, Vector{Int64}, Vector{Real}}
└── return %2

julia> @btime (x -> x.first).($z);
  574.302 ns (1 allocation: 7.94 KiB)

julia> @btime getfield.($z,:first); # just to compare with the previous
  22.500 μs (1002 allocations: 39.20 KiB)

```

Notice the non-concrete return type and the type instability this function introduces. Const-prop does not always work well through broadcast, so broadcasting `getfield`/`getproperty` can often introduce type instability that a dedicated accessor does not.

Of course, the initial example had a struct with untyped fields so instability was inevitable there. But here I’ve shown that broadcasting fails even where it _could_ succeed.

---

<div class="post-metadata">

### Author: ![mike.ingold](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mike.ingold/32/203749_2.png) [@mike.ingold](https://discourse.julialang.org/u/mike.ingold)
#### Post date: [December 7, 2023, 9:21pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/26 "2023-12-07T21:21:26Z")

</div>

I figured out where I read that, in a Julia PR by @stevengj:

[RFC: curry underscore arguments to create anonymous functions by stevengj · Pull Request #24990 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/24990)

Maybe I mis-read the parenthesized sentence in the first paragraph as indicating this part of the functionality was already a thing.

---

<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: [December 7, 2023, 9:37pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/27 "2023-12-07T21:37:18Z")

</div>

`getfield.(z,(:first,))` improves the inference to `Union{Vector{Int}, Vector{Real}}` and ends up as performant, but the inference isn’t as good as `Vector{Int}` of the broadcasted anonymous function. A constant literal symbol in the method body really does help the compiler more than a container of symbols. Since `getproperty`/`getfield` already exceptionally specializes on constant field names or integers, I imagine this optimization could exist for broadcasted `getproperty`/`getfield`?

---

<div class="post-metadata">

### Author: ![Marco\_Antoniotti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marco_antoniotti/32/18697_2.png) [@Marco\_Antoniotti](https://discourse.julialang.org/u/Marco_Antoniotti)
#### Post date: [December 7, 2023, 9:38pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/28 "2023-12-07T21:38:58Z")

</div>

Now you know how much of an old geezer I am 😊

---

<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: [December 7, 2023, 10:08pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/29 "2023-12-07T22:08:14Z")

</div>

If you were talking about Lisp, I can’t find what you’re referring to by automatic generation of accessor methods. The closest I could look up was `defclass` specifying `:reader, :writer, :accessor` per slot, but those are manually specified with arbitrary names.

---

<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: [December 7, 2023, 11:11pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/30 "2023-12-07T23:11:18Z")

</div>

> [@mikmoore](#):
>
> `x -> x.first`

I don’t get it. `x.first` calls `getfield(x, :first)`, how does the extra level of indirection help?

---

<div class="post-metadata">

### Author: ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)
#### Post date: [December 7, 2023, 11:41pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/31 "2023-12-07T23:41:34Z")

</div>

I think it has to do with `typeof(getfield(::T, ::Symbol))` being impossible in the type domain (for heterogeneous field types) because all that is known about the field is that it is `::Symbol`. Constant propagation (or concrete evaluation or some other related concept that is subtly different that I wouldn’t identify correctly) of the specific _value_ of the `Symbol` is necessary to know which field (and type) is being accessed. If sufficient optimization magic occurs, then this all works out. But broadcasting seems to usually be enough to prevent this.

Or maybe I’m quite wrong as to the technical reason. I just know this can be unreliable because I’ve been bit by it before.

Whatever it is, there’s a related issue [#43333](https://github.com/JuliaLang/julia/issues/43333). There is a comment there that provides a tad more detail, but I’m not qualified to explain it here.

---

<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: [December 8, 2023, 8:45am UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/32 "2023-12-08T08:45:23Z")

</div>

I’m not convinced that the problem is only with `getfield`:

```julia
julia> getfirst_scalar(z) = getfield(z, :first);

julia> getfirst_broadcasted(z) = getfield.(z, :first);

julia> @btime getfirst_scalar.($z);
  831.818 ns (1 allocation: 7.94 KiB)

julia> @btime getfirst_broadcasted($z);
  30.800 μs (1002 allocations: 39.20 KiB)

julia> @btime getfirst_broadcasted.($z); # <- extra dot
  2.278 μs (1 allocation: 7.94 KiB)

```

This is strange to me. I mean, `getifield` is _the_ mechanism for accessing fields. _Everything_ builds on this, how can it be slow?

---

<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: [December 8, 2023, 1:55pm UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/33 "2023-12-08T13:55:46Z")

</div>

> [@DNF](#):
>
> I don’t get it. `x.first` calls `getfield(x, :first)`, how does the extra level of indirection help?

`x.first` gets lowered into `getproperty(x, :first)` which then calls `getfield(x, :first)`. We can infer the type returned due to constant propagation.

```julia-repl

julia> struct Foo end

julia> f() = Foo().first
f (generic function with 1 method)

julia> @code_lowered f()
CodeInfo(
1 ─ %1 = Main.Foo()
│ %2 = Base.getproperty(%1, :first)
└── return %2
)

julia> Base.getproperty(::Foo, s::Symbol) = 42

julia> f()
42

julia> @code_warntype f()
MethodInstance for f()
  from f() @ Main REPL[2]:1
Arguments
  #self#::Core.Const(f)
Body::Int64
1 ─ %1 = Main.Foo()::Core.Const(Foo())
│ %2 = Base.getproperty(%1, :first)::Core.Const(42)
└── return %2

```

We can even complicate the situation as follows.

```julia-repl
julia> Base.getproperty(::Foo, s::Symbol) = s == :first ? 42 : π

julia> @code_warntype f()
MethodInstance for f()
  from f() @ Main REPL[2]:1
Arguments
  #self#::Core.Const(f)
Body::Int64
1 ─ %1 = Main.Foo()::Core.Const(Foo())
│ %2 = Base.getproperty(%1, :first)::Core.Const(42)
└── return %2

julia> g() = Foo().the_answer_to_everything_else
g (generic function with 1 method)

julia> @code_warntype g()
MethodInstance for g()
  from g() @ Main REPL[13]:1
Arguments
  #self#::Core.Const(g)
Body::Irrational{:π}
1 ─ %1 = Main.Foo()::Core.Const(Foo())
│ %2 = Base.getproperty(%1, :the_answer_to_everything_else)::Core.Const(π)
└── return %2

```

---

<div class="post-metadata">

### Author: ![Marco\_Antoniotti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marco_antoniotti/32/18697_2.png) [@Marco\_Antoniotti](https://discourse.julialang.org/u/Marco_Antoniotti)
#### Post date: [April 27, 2025, 10:16am UTC](https://discourse.julialang.org/t/mapping-a-getter/107276/34 "2025-04-27T10:16:18Z")

</div>

Hi @Benny … Just stumbled on this old thread.

I **am** talking about Common Lisp. `defstruct` is what you need to look at (it also has single inheritance). As per `defclass` you are right, but `def class` is just … a macro away (look ma: no `@`).

Cheers  
MA

[Previous page](https://discourse.julialang.org/t/mapping-a-getter/107276.md?page=1)
