# Julep: Efficient Hierarchical Mutable Data

**URL:** <https://discourse.julialang.org/t/julep-efficient-hierarchical-mutable-data/125190>\
**Category:** Internals & Design\
**Tags:** mutable-structure, rfc\
**Created:** [January 25, 2025, 6:51am UTC](https://discourse.julialang.org/t/julep-efficient-hierarchical-mutable-data/125190 "2025-01-25T06:51:04Z")\
**Posts on this page:** 1\
**Showing post:** 26

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [January 27, 2025, 9:48pm UTC](https://discourse.julialang.org/t/julep-efficient-hierarchical-mutable-data/125190/26 "2025-01-27T21:48:54Z")

</div>

I had some time this morning to try my hand at a macro-based solution. I believe that it’s 90% there (with the remaining 90% conceptually straight-forward), and mostly in line with the original proposal.

```julia
julia> abstract type AbstractElephant end

julia> struct Elephant <: AbstractElephant
           hunger::Float64
       end

julia> @with_inline_types mutable struct Forest
           elephant::@inline(Elephant) # @inline elephant::Elephant also possible, but less
                                         # MacroTools-friendly
           normal_field::String
       end
Forest

julia> @with_inline_types mutable struct Biome
           forest::@inline(Forest)
       end
Biome

julia> f = Forest(Elephant(2), "Zoulando") # TODO: overload Base.show
Forest(2.0, "Zoulando")

julia> dump(f)
Forest
  elephant__hunger: Float64 2.0
  normal_field: String "Zoulando"

julia> f.elephant
ElephantViewFromForest(Forest(30.0, "Zoulando"))

julia> f.elephant.hunger = 30
30

julia> f.elephant.hunger
30.0

julia> b = Biome(Forest(Elephant(50), "Tasmania"))
Biome(50.0, "Tasmania")

julia> fieldnames(Biome)
(:forest __elephant__ hunger, :forest__normal_field)

```

Nested inline structs work out for construction, but field access is broken ATM. AFAICT views don’t compose, and we’d have to define `ElephantViewFromForestFromBiome`. Not very difficult, but a bit of a drag.

`@btime` looks good (no allocation) for access. `Biome(Forest(Elephant(50), "Tasmania"))` does create a needless `Forest` object. It’d need some rethinking to avoid that.

Parametric structs would be significant work.

> ****
>
> ```julia
> using MacroTools
> using MacroTools: @qq
> using Base.Meta: quot
> 
> """ Defined for types, not objects. """
> static_propertynames(::Type{T}) where T = fieldnames(T)
> 
> macro with_inline_types(def)
> # Example with respect to this
> # @with_inline_types struct Forest
> # elephant::@inline(Elephant)
> # normal_field::String
> # end
> 
> di = MacroTools.splitstructdef(def)
> di2 = copy(di)
> name = di[:name] # Forest
> inline_code = Expr[] # code to define the type views
> di2[:fields] = [] # [(normal_field, Int)]
> inlined_type_fields = Pair[] # [:elephant=>ElephatViewFromForest]
> prop_names = Symbol[] # [:elephant, :normal_field]
> constr_accessors = []
> 
> for (f, type) in di[:fields]
> push!(prop_names, f)
> if @capture(type, @inline(Texpr_))
> T = eval(Texpr) # having to `eval` is unfortunate. there are other ways worth
> # investigating, such as `getfield(@ __MODULE__ , Texpr)`, that would
> # have different trade-offs
> s_fields = [Symbol(f, "__", sub_f) for sub_f in fieldnames(T)] # [:elephant__hunger]
> view_name = Symbol(Texpr, "ViewFrom", name) # technically should also append __f
> type_view_code = quote
> struct $view_name <: $(supertype(T)) # ElephantViewFromForest <: AbstractElephant
> obj::$name
> end
> function Base.getproperty(view::$view_name, p::Symbol)
> # if p == :hunger
> # return getfield(view, :obj).elephant__hunger
> # end
> # error("Field not found")
> $([:(if p === $(quot(fieldname))
> return getfield(view, :obj).$s_fieldname
> end)
> for (fieldname, s_fieldname) in zip(static_propertynames(T), s_fields)]...)
> error("type ", $Texpr, " has no property ", p)
> end
> Base.propertynames(view::$view_name) = $(fieldnames(T))
> function Base.setproperty!(view::$view_name, p::Symbol, x)
> $([:(if p === $(quot(fieldname))
> return getfield(view, :obj).$s_fieldname = x
> end)
> for (fieldname, s_fieldname) in zip(static_propertynames(T), s_fields)]...)
> error("type ", $Texpr, " has no property ", p)
> end
> end
> for s_field in fieldnames(T)
> push!(constr_accessors, :(getfield($f, $(quot(s_field)))))
> end
> push!(inline_code, type_view_code)
> push!(inlined_type_fields, f => view_name)
> append!(di2[:fields], map(tuple, s_fields, fieldtypes(T)))
> else
> push!(constr_accessors, f)
> push!(di2[:fields], (f, type))
> end
> end
> @qq(begin # @qq to get good line numbers
> $(MacroTools.combinestructdef(di2))
> $(inline_code...)
> function Base.getproperty(obj::$name, p::Symbol)
> # if p == :elephant
> # return ElephantViewFromForest(obj)
> # end
> # return getfield(obj, p)
> $([:(if p === $(quot(fieldname))
> return $sub_type(obj)
> end)
> for (fieldname, sub_type) in inlined_type_fields]...)
> return Base.getfield(obj, p)
> end
> Base.propertynames(obj::$name) = $prop_names
> static_propertynames(::Type{$name}) = $prop_names
> function $name($(prop_names...))
> $name($(constr_accessors...))
> end
> $name
> end) |> esc
> end
> 
> ```

---

_[View the full topic](https://discourse.julialang.org/t/julep-efficient-hierarchical-mutable-data/125190)._
