Anonymous nested struct

By “Anonymous nested struct” I mean defining a struct that has another struct with no name inside:

struct ABC
    name::String
    data::struct
        raw::Vector{Float64}
        value::Int
    end
end

I wonder such a concept is under discussion among Julia designers.

I believe allowing anonymous nested struct should make life much easier and make code more readable compared to these:

struct ABC
    name::String
    data::Tuple{Vector{Float64}, Int}
end

or

struct Data
    raw::Vector{Float64}
    value::Int
end

struct ABC
    name::String
    data::Data
end
``

Maybe there is a reason to have something like this that I can’t immediately think of, but as far as readability goes I find your third example much more readable and aesthetically pleasing than the first.

1 Like

Named tuples are Julia’s anonymous structs:

struct ABC
   name::String
   data::NamedTuple{(:raw,:value),Tuple{Vector{Float64},Int}}
end

from which you can do:

julia> abc = ABC("foo",(raw=[3,4,5],value=17))
ABC("foo", (raw = [3.0, 4.0, 5.0], value = 17))

julia> abc.data.value
17
5 Likes

Ah, yes, indeed NamedTuples are anonymous structs. But aesthetically, I still feel NamedTuple syntax looks a little bit cluttered than the first example. But I agree that it’s subjective and arguable.

Possible benefit of the first example is 1) fieldname and type are side by side 2) when deeply nested, it is more readable than NamedTuple:

struct ABC
    name::String
    data::struct
        raw::Vector{Float64}
        value::Int
        innerFields::struct
            val::Int
            name::String
            data::struct
                arr:Array{Float64, 3}
            end
        end
    end
end

Equivalent NamedTuple exression would be rather complex.

1 Like

It could be, but downside is we have to give it a name, sometimes could be annoying I suppose.

You can use typeof:

struct ABC
    name::String
    data::typeof((
        raw = [0.0],
        value = 0,
        innerFields = (val = 0, name = "", data = (arr = zeros(0, 0, 0),)),
    ))
end

1 Like

It could be one option…but maybe a little bothersome in that it requires to provide literals manually as part of the struct definition.

All of that is valid syntax, so a macro could rewrite the inner structs to named tuples.

5 Likes

Yes, a macro might be useful here. For example:

macro NamedTuple(ex)
    Meta.isexpr(ex, :block) || error("@NamedTuple requires a begin-end block")
    decls = filter(e -> !(e isa LineNumberNode), ex.args)
    all(e -> Meta.isexpr(e, :(::)), decls) || error("@NamedTuple requires a block of name::type expressions")
    vars = [QuoteNode(e.args[1]) for e in decls]
    types = [e.args[2] for e in decls]
    return :(NamedTuple{($(vars...),), Tuple{$(types...)}})
end

allows you to do:

julia> @NamedTuple begin
           raw::Vector{Float64}
           value::Int
       end
NamedTuple{(:raw, :value),Tuple{Array{Float64,1},Int64}}

It might be reasonable to include something like this in Base, if someone wants to put together a PR.

See also https://github.com/JuliaLang/julia/issues/34498 regarding an alternative syntax @NamedTuple{raw::Vector{Float64}, value::Int} that might be a bit nicer. (Of course, a single macro could easily support both {...} and begin ... end syntaxes, just by using Meta.isexpr(ex, :block) || Meta.isexpr(ex, :brace) on the first line.)

Update: PR created

14 Likes

Glad to know that similar idea was already thought about!
If would be great if it is capable of nesting like:

struct ABC
	name::String
	data::@NamedTuple begin
		raw::Vector{Float64}, 
		value::Int
		data::@NamedTuple begin
			arr::Vector{Float64};
		end
	end
end

if @NamedTuple could be made nested in struct, it should certainly be possible to nest struct for less typing for example:

@nested struct
    field::struct

    end 
end

Meta programming is a wonderful tool but also too mind-bending world to me.

It is:

julia> @NamedTuple begin
                       raw::Vector{Float64}
                       value::Int
                       data::@NamedTuple begin
                               arr::Vector{Float64};
                       end
                   end
NamedTuple{(:raw, :value, :data),Tuple{Array{Float64,1},Int64,NamedTuple{(:arr,),Tuple{Array{Float64,1}}}}}
3 Likes

It works! why didn’t it work yesterday? I ran the code on a computer with Julia 1.1.1 installed. Now I run it on Julia 1.3 and confirm it works. I am staring at your code to understand it. :slight_smile: Its should go to Base.

The code you posted above had an extra comma on the raw line.