# Recursive Accessor descend condition

**URL:** <https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232>\
**Category:** General Usage\
**Created:** [April 20, 2025, 12:02am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232 "2025-04-20T00:02:55Z")\
**Posts on this page:** 19\
**Page:** 1

<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:** [April 20, 2025, 12:02am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/1 "2025-04-20T00:02:55Z")

</div>

Is it possible to write this with `Accessors.Recursive`?

```julia
using Accessors

modifyrec(f, x) =
    if hasproperty(x, :bs)
        (@modify(x.bs |> Elements()) do b
            modifyrec(f, b)
        end) |> f
    else
        f(x)
    end

let data = (;a=1, bs=[(;a=2, bs=[(;a=3, bs=[(;a=4)])])])
    modifyrec(data) do x
        @set x.a = 100 * x.a
    end
end
# (a = 100, bs = [(a = 200, bs = [(a = 300, bs = [(a = 400,)])])])

```

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 20, 2025, 12:25am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/2 "2025-04-20T00:25:07Z")

</div>

Imo `Accessors.Recursive` isn’t the most generic or straightforward to use…

I’d suggest this:

```julia
julia> using AccessorsExtra

julia> data = (;a=1, bs=[(;a=2, bs=[(;a=3, bs=[(;a=4)])])])

julia> @modify(a -> 100*a, data |> RecursiveOfType(NamedTuple, order=:pre) |> _.a)
(a = 100, bs = [(a = 200, bs = [(a = 300, bs = [(a = 400,)])])])

```

---

<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:** [April 20, 2025, 12:35am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/3 "2025-04-20T00:35:06Z")

</div>

I would still like to specify that recursion happens on `_.bs |> Elements()` if present. Is that possible? Ie, the “descend condition” should be `hasproperty(_, :bs)` and the way to descend is examining the elements of `_.bs`.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 20, 2025, 12:52am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/4 "2025-04-20T00:52:19Z")

</div>

> [@jar1](#):
>
> the “descend condition” should be `hasproperty(_, :bs)`

Use either `RecursiveOfType(NamedTuple) |> If(x -> hasproperty(x, :bs))` or `RecursivePred(x -> hasproperty(x, :bs))`.

> [@jar1](#):
>
> the way to descend is examining the elements of `_.bs`

Use `RecursivePred(x -> hasproperty(x, :bs), Elements() ∘ (@maybe _.bs), order=:pre)` or analogously with `RecursiveOfType`.  
Btw, I never – not even once – needed to explicitly specify the way to descend in practice. Maybe it’s not necessary in your case as well?

* * *

Note: inference may start suffering with complex queries at some point…

---

<div class="post-metadata">

**Author:** ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)\
**Post date:** [April 20, 2025, 6:31am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/5 "2025-04-20T06:31:40Z")

</div>

How should a case like this be treated?

```julia
data = (;a=1, bs=[(;a=2, bs=[(;a=3, bs=[(;a=4)])])],cs=(;a=5,bs=(;a=6)))

```

---

<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:** [April 20, 2025, 7:38am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/6 "2025-04-20T07:38:28Z")

</div>

> [@rocco\_sprmnt21](#):
>
> How should a case like this be treated?

Right, good question.

```julia
modifyrec(data) do x
  @set x.a = 100 * x.a
end
(
  a = 100, 
  bs = [(a = 200, bs = [(a = 300, bs = [(a = 400,)])])], 
  cs = (a = 5, bs = (a = 6,)),
)
modify(f, data, RecursivePred(Returns(true), Elements()∘(@maybe _.bs); order=:post)) # good
# (a = 100, bs = [(a = 200, bs = [(a = 300, bs = [(a = 400,)])])], cs = (a = 5, bs = (a = 6,)))

```

I think in my cases the key or propertyname is how I know the semantics of the value inside. The `typeof` a value does not capture much about its interpretation; it’s the interface and more importantly its relation to its context – namely its access path optic `_.bs` – that tells me what the data means and hence what `modify`ing it would mean.

For example if it’s some big json I can follow a path of `.children[*].children[*].children[*]...` but I wouldn’t want to apply a transformation all over a whole tree `*[*].*[*].*` lest it accidentally grab some other field `.children[100].stepchildren[*].children[]` either in the present document or added in a later edition of the data.

---

<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:** [April 20, 2025, 7:42am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/7 "2025-04-20T07:42:04Z")

</div>

By the way what’s happening with the `order=nothing` case here? I was guessing it would default to one or the other but it seems to do something else.

```julia
julia> let data = (;a=1, bs=[(;a=2, bs=[(;a=3, bs=[(;a=4)])])],cs=(;a=5,bs=(;a=6)))
           f(x) = @set x.a = 100 * x.a
           modifyrec(f, data)
           [
               modify(f, data, RecursivePred(x -> x.a ≤ 3, Elements()∘(@maybe _.bs); order=:pre)) ,
               modify(f, data, RecursivePred(x -> x.a ≤ 3, Elements()∘(@maybe _.bs); order=:post)) ,
               modify(f, data, RecursivePred(x -> x.a ≤ 3, Elements()∘(@maybe _.bs); order=nothing)) 
           ]
       
       end
3-element Vector{@NamedTuple{a::Int64, bs::Vector{@NamedTuple{a::Int64, bs::Vector{@NamedTuple{a::Int64, bs::Vector{@NamedTuple{a::Int64}}}}}}, cs::@NamedTuple{a::Int64, bs::@NamedTuple{a::Int64}}}}:
 (a = 100, bs = [(a = 200, bs = [(a = 300, bs = [(a = 4,)])])], cs = (a = 5, bs = (a = 6,)))
 (a = 100, bs = [(a = 200, bs = [(a = 300, bs = [(a = 4,)])])], cs = (a = 5, bs = (a = 6,)))
 (a = 100, bs = [(a = 2, bs = [(a = 3, bs = [(a = 4,)])])], cs = (a = 5, bs = (a = 6,)))

```

---

<div class="post-metadata">

**Author:** ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)\
**Post date:** [April 20, 2025, 12:38pm UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/8 "2025-04-20T12:38:46Z")

</div>

is it possible to apply (in a “simply” way with the Accessor syntax) the function `f()` to every key `:a` that has `:bs` as a parent?

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 21, 2025, 3:32am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/9 "2025-04-21T03:32:58Z")

</div>

> [@jar1](#):
>
> By the way what’s happening with the `order=nothing` case here? I was guessing it would default to one or the other but it seems to do something else.

`order=nothing` doesn’t descend into matching values at all. This is arguably the most natural and intuitive default: you generally want `modify(x -> 2*x, data, RecursiveOfType(Number))` to multiply numbers by two only once, not several times for stuff like `complex(10u"m")`.

I haven’t documented this flag originally because wasn’t sure that `order=:pre/:post/nothing` is the right/useful set of choices. Over time it seems quite natural, probably it should finally be documented… And ideally, when the semantics of recursive optics is fleshed out and clear, they can be upstreamed to Accessors proper.

---

<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:** [April 21, 2025, 8:42pm UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/10 "2025-04-21T20:42:26Z")

</div>

I like the idea of specifying traversal order and the predicate specification, as you’re showing there.

That said I was worried about `.stepchildren` and @rocco_sprmnt21’s `.cs` above and I think `complex(10m)` is another example. Early stopping with `order=nothing` is one way to solve it but it still makes me nervous to specify “what you’ll find there” versus “how to get there” because there might be false positives.

My instinct might be something like

```julia
RecursivePre(p, optic) = ...
RecursivePost(p, optic) = ...
RecursivePre(optic) = RecursivePre(Returns(true), optic)
RecursivePost(optic) = RecursivePost(Returns(true), optic)

```

because

- I can specify how to descend
- don’t need to specify a stopping condition if I don’t need one
- it avoids flag kwargs
- `nothing` isn’t really an order

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 21, 2025, 8:53pm UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/11 "2025-04-21T20:53:25Z")

</div>

> [@jar1](#):
>
> `nothing` isn’t really an order

Sure, probably best to call it `:nodescent` or something along those lines 🙂

> [@jar1](#):
>
> it avoids flag kwargs

Idk, why having three types that dispatch to the same code is better than having a kwarg with three possible values?

> [@jar1](#):
>
> My instinct might be something like  
> \<…\>

Could you please clarify and motivate the differences from the current state?  
I catch two differences:

- kwargs with three possible values → three types
- 1-arg constructor fixes the first argument (predicate) instead of the second (optic).

Anything I missed?

I’m not against changes here, but would like to understand both the actual suggestions and the underlying reasoning.

---

<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:** [April 21, 2025, 10:27pm UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/12 "2025-04-21T22:27:47Z")

</div>

> [@aplavin](#):
>
> I catch two differences:
> 
> - kwargs with three possible values → three types
> - 1-arg constructor fixes the first argument (predicate) instead of the second (optic).

Yeah I think that’s all I’m saying there, though the “yolo”-style recursion I was knocking above makes me think we must be coming from totally different planets, and I should hold my tongue till I understand your position more. I’m curious what kind of data and applications justify that approach.

* * *

If I understand, the current `RecursivePred` is more or less

```julia-auto
children(x::NamedTuple) = collect(x)
children(x::Vector) = copy(x)
children(x::Int) = Int[]

Accessors.set(x::NamedTuple, ::typeof(children), val::Array) = NamedTuple{propertynames(x)}(Tuple(val))
Accessors.set(x::Array, ::typeof(children), val::Array) = copy(val)
Accessors.set(x::Int, ::typeof(children), val::Array) = x

rec_nodescend(f, p, x) = let
    if p(x)
        f(x)
    else
        @set children(x) = rec_nodescend.(f, p, children(x))
    end
end

rec_pre(f, p, x) = let
    x = @set children(x) = rec_pre.(f, p, children(x))
    p(x) ? f(x) : x
end

let
    data = (;a=1, bs=[(;a=2, bs=[(;a=3, bs=[(;a=4)])])],cs=(;a=5,bs=(;a=6)))
    [
        rec_nodescend((x->100x), (x -> x isa Number), data),
        rec_pre((x->100x), (x -> x isa Number), data)
    ]
end
2-element Vector{@NamedTuple{a::Int64, bs::Vector{@NamedTuple{a::Int64, bs::Vector{@NamedTuple{a::Int64, bs::Vector{@NamedTuple{a::Int64}}}}}}, cs::@NamedTuple{a::Int64, bs::@NamedTuple{a::Int64}}}}:
 (a = 100, bs = [(a = 200, bs = [(a = 300, bs = [(a = 400,)])])], cs = (a = 500, bs = (a = 600,)))
 (a = 100, bs = [(a = 200, bs = [(a = 300, bs = [(a = 400,)])])], cs = (a = 500, bs = (a = 600,)))

```

> [@aplavin](#):
>
> Idk, why having three types that dispatch to the same code is better than having a kwarg with three possible values?

My general tendency is to avoid flag kwargs for somewhat “taste”-type reasons. Part of the motivation for this practice is to let the type checker help, and because I think “stringly”-typed stuff is just a little ugly.

But also for “inversion of control” reasons—do we have a proof that there are only two (or three) “orders” for these operations such that users won’t want to supply their own?:

“Order” is currently being used for both (1) deciding whether `f` applies to `x` or `y` where `y = @set children(x) = f.(children(x))`, and (2) whether to apply `f` to the children of `x`, in the case of `:nodescend`. For example does `p` need to be evaluated before `f` is applied to `x's` children or could we decide whether to apply `f` to the current value based on the result of apply `f` to `x`’s children?: `p(y) ? f(y) : y`. Do we also want `:noascend`?

If this isn’t a closed enum of recursion strategies, extensible external control might be warranted, versus internal branching on a symbol?

I’m more or less just going from my gut here, so please take all these comments with a large grain of salt.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 22, 2025, 12:25am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/13 "2025-04-22T00:25:01Z")

</div>

> [@jar1](#):
>
> If I understand, the current `RecursivePred` is more or less \<…\>

Correct, for the `modify(f, x, RecursivePred)` part. As an optic, it also supports `getall()` and `setall()`, and composes with others.

> [@jar1](#):
>
> the “yolo”-style recursion I was knocking above makes me think we must be coming from totally different planets, and I should hold my tongue till I understand your position more. I’m curious what kind of data and applications justify that approach.

Indeed, I think I remember your opinion that it’s “illegal” to access arbitrary properties in Julia 🙂 This question may be debatable in principle, but here I look from a more pragmatic PoV.

Just searched for `RecursiveOfType` occurrences in my code, and all the constructor calls are single-argument – never (literally not even once!) I had to specify the second parameter, customizing the descent rule. Feels like a good argument for this being the default 🙂

Some examples include:

```julia
# conversion:
modify(Float32, x, RecursiveOfType(Float64))
modify(String, x, RecursiveOfType(AbstractString))

# replace distributions/uncertainties with some representative value
modify(mean, x, RecursiveOfType(Distribution))
modify(value, x, RecursiveOfType(Uncertain.Value))

# in https://github.com/baggepinnen/MonteCarloMeasurements.jl/pull/173/files
@getall x |> RecursiveOfType(Particles) |> length(_.particles)
modify(x, RecursiveOfType(Particles)) do p
    p.particles[i]
end

# for quick ad-hoc optimization:
rawvals = getall(x, RecursiveOfType(AbstractFloat))
... rawvals is a vector/tuple of floats ...
setall(x, RecursiveOfType(AbstractFloat), rawvals)

# AST manipulation
# get all function calls:
@getall x |> RecursivePred(e -> Base.isexpr(e, :call); order=:pre) |> _.args[1]
# wrap all function calls as myfunc(f, args...):
modify(x, RecursivePred(e -> Base.isexpr(e, :call); order=:pre)) do call
    :(myfunc($(call.args...)))
end

# JSON3 to Dictionary conversion
modify(Dictionary, JSON3.read(...), RecursiveOfType(JSON3.Object; order=:pre))

```

It’s just so convenient to tell what kind of object you want to extract/modify, no matter how exactly it can be reached within the structure!

I wonder what your potential usecases are, and why you think automatic descent won’t work there.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 22, 2025, 12:31am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/14 "2025-04-22T00:31:10Z")

</div>

> [@jar1](#):
>
> “Order” is currently being used for both (1) deciding whether `f` applies to `x` or `y` where `y = @set children(x) = f.(children(x))`, and (2) whether to apply `f` to the children of `x`, in the case of `:nodescend`.

I guess order isn’t really the best name here (suggestions welcome!).  
It defines one thing: what to do when at some step the “current” object `x` matches the predicate. Options are (for `modify`):

1. replace `x` with `f(x)`
2. replace `x` with `f(x)`; then descend into `f(x)` and replace it with the result
3. descend into `x` and replace it with the result `x'`; then replace it with `f(x')`

I think I only used #1 (`nothing`) and #3 (`:pre`).  
These three seem to be reasonably generic and tightly related, so they are set with a single argument now.

---

<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:** [April 22, 2025, 6:39pm UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/15 "2025-04-22T18:39:11Z")

</div>

> [@aplavin](#):
>
> Some examples include:

Thanks for digging these up. I think these snippets show the kind of values sought, rather than the context they’re in, and it’s the context that I’m mostly worried about. I’m inferring that in most of those cases, you’re confident its safe to match on types because

- (1) you control the data values and (2) what types they have, and (3) you don’t expect the data types to gain or lose `propertynames` in the future, or
- in the json case, you don’t control the data, but (5) the types are restricted and (6) you’re not doing semantically significant changes (just converting from `Object` to `Dictionary`)

Is that basically your correctness argument, or is there something else?

> [@aplavin](#):
>
> I wonder what your potential usecases are, and why you think automatic descent won’t work there.

I don’t want to bore you to death by rehashing my concerns, but since you ask…🙂 :

#### False positive matches

How can I be confident that in all the data that this code will run on, there won’t be any numbers other than the ones I want?

- _False positive data._ Say I get a large JSON response from some web API. I’m only interested in one descent pathway `.children` and I don’t want to read the docs for all the other potentially many other fields and subfields that optionally appear in this object. A hidden embedded `.stepchildren` field spoils my `.children` genealogy:

```julia-auto
data = (;age=1, children=[(;age=2, children=[(;age=3, children=[(;age=4, children=[(;children=[(;age=5, children=[], stepchildren=[(;age=100, children=[])])])])])])])

mean(getall(data, RecursiveOfType(Int))) # secret stepchild

```

- _False positive metadata._ `Dictionary` has plenty of internal `Number`s like `.indices.hashes` etc and I wouldn’t want to accidentally `getall` those. This returns all sorts of internal numbers:

```julia-auto
let d = dictionary([:a => dictionary([:x => [100]]), :b => dictionary([:x => [101]])])
    getall(d, RecursiveOfType(Int))
end

```

#### False negative matches

- _Data-value types_: If I’m querying a JSON for `RecursiveOfType(Integer)` and then `JSON3.read` decides to parse json `"3.0"` as `Float64` instead of `Int64`, I’ll miss it and get the wrong value.

- _Library changes:_ How can I be confident that the values I want will always have type `JSON3.Object` rather than having been replaced by a `JSON3.NewObject` when JSON3.jl introduced a new type, and then they’ll be missed and I’ll get the wrong result?

Thanks for listening. 🙂

---

<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:** [April 22, 2025, 7:53pm UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/16 "2025-04-22T19:53:01Z")

</div>

> [@aplavin](#):
>
> 1. descend into `x` and replace it with the result `x'`; then replace it with `f(x')`
> 
> I think I only used #1 (`nothing`) and #3 (`:pre`).

Isn’t #3 a [post-order traversal](https://en.wikipedia.org/wiki/Tree_traversal#Post-order,_LRN), since it applies `f` on `x'` instead of on `x`?

Would [`BreadthFirst()`](https://en.wikipedia.org/wiki/Breadth-first_search) be another acceptable `order`?

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 23, 2025, 1:18am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/17 "2025-04-23T01:18:35Z")

</div>

> [@jar1](#):
>
> I think these snippets show the kind of values sought, rather than the context they’re in, and it’s the context that I’m mostly worried about.

Indeed, that was my point 🙂 Looking forward to a real/realistic examples when this automatic descent doesn’t work!  
_Preferably, concrete usecases one may encounter in practice._

> [@jar1](#):
>
> you’re confident its safe to match on types because
> 
> - (1) you control the data values and (2) what types they have, and (3) you don’t expect the data types to gain or lose `propertynames` in the future,

Not really… The main reason to use recursive descent here is because the type and exact structure of the data may change, and I don’t want to manually write and update all relevant paths.  
Types (2) and propertynames (3) may easily change: from the most basic case of namedtuples with arbitrary properties, to more involved changes to structs in question.

> [@jar1](#):
>
> in the json case, you don’t control the data, but (5) the types are restricted and (6) you’re not doing semantically significant changes (just converting from `Object` to `Dictionary`)

I guess this was the case in my JSON example indeed…  
Also remember that it’s not just `modify`: one could write a reasonable query like

```julia
@getall JSON3.read(...) |> RecursiveOfType(JSON3.Object) |> If(x -> x[:kind] == :mykind) |> _[:mykey]

```

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 23, 2025, 1:24am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/18 "2025-04-23T01:24:35Z")

</div>

> [@jar1](#):
>
> #### False positive matches
> 
> How can I be confident that in all the data that this code will run on, there won’t be any numbers other than the ones I want?  
> \<…\>

> [@jar1](#):
>
> **False negative matches**
> 
> _Library changes:_ How can I be confident that the values I want will always have type `JSON3.Object` rather than having been replaced by a `JSON3.NewObject` when [JSON3.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/JSON3) introduced a new type, and then they’ll be missed and I’ll get the wrong result?  
> \<…\>

Well, if something unexpected can change in your data or library, you need to check and potentially update your code. Doesn’t seem specific to optics or recursive descent at all!

> [@jar1](#):
>
> _False positive metadata._ `Dictionary` has plenty of internal `Number`s like `.indices.hashes` etc and I wouldn’t want to accidentally `getall` those.

Then don’t use catch-all recursive descent on these types 🙂 Remember that using `Children()` to descend is just the default, one is free to provide any optic there.

For containers specifically, it makes sense to define children as elements instead of properties. This is easy to add and already done for arrays ([AccessorsExtra.jl/src/recursive.jl at master · JuliaAPlavin/AccessorsExtra.jl · GitHub](https://github.com/JuliaAPlavin/AccessorsExtra.jl/blob/master/src/recursive.jl#L11)) as they don’t have properties at all.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 23, 2025, 1:29am UTC](https://discourse.julialang.org/t/recursive-accessor-descend-condition/128232/19 "2025-04-23T01:29:33Z")

</div>

> [@jar1](#):
>
> Isn’t #3 a [post-order traversal](https://en.wikipedia.org/wiki/Tree_traversal#Post-order,_LRN), since it applies `f` on `x'` instead of on `x`?

Sure, you are right, thanks for catching!

> [@jar1](#):
>
> Would [`DepthFirst()`](https://en.wikipedia.org/wiki/Breadth-first_search) be another acceptable `order`?

Idk, what would be the difference in the result? And do you have some potential applications in mind?
