Static fieldnames

Imagine I have a

struct A
    a::Int 
    b::String 
end

I want to stringify it like this:
string(A.a, A.b)

However, I want to do this WITHOUT prior knowledge of the fieldnames of A. This is quite difficult to do in a type-stable way. It would be much easier if for any type one could generate a tuple Val{:a}(), Val{:b}() where a and b are fieldnames. Then the fieldnames would be known to the compiler. Through the magic of tuples, it would in fact be quite easy to do type stably:

inner_type(::Val{T}) where T = T

function stringify(object)
    value_fields = get_value_fields(typeof(object))
    string(map(value_fields) do value_field
        getindex(object, inner_type(value_field))
    end...)
end

Is this possible somehow?

julia> @generated f(obj) = :(string($((:(getfield(obj, $i)) for i in 1:fieldcount(obj))...)))
f (generic function with 1 method)

julia> f(1im)
"01"

julia> f((1, 2, 2.4))
"122.4"

julia> @code_warntype f(1im)
Variables:
  #self# <optimized out>
  obj::Complex{Int64}

Body:
  begin
      #= REPL[4]:1 =#
      # meta: location REPL[4] @generated body
      return $(Expr(:invoke, MethodInstance for #print_to_string#245(::Void, ::Function, ::Int64, ::Vararg{Int64,N} where N), :(Base.#print_to_string#245), :($(QuoteNode(nothing))), :($(QuoteNode(Base.print_to_string))), :((Main.getfield)(obj, 1)::Int64), :((Main.getfield)(obj, 2)::Int64)))::String
      # meta: pop location
  end::String

julia> @code_warntype f((1, 2, "", 3))
Variables:
  #self# <optimized out>
  obj::Tuple{Int64,Int64,String,Int64}

Body:
  begin
      #= REPL[4]:1 =#
      # meta: location REPL[4] @generated body
      return $(Expr(:invoke, MethodInstance for #print_to_string#245(::Void, ::Function, ::Int64, ::Vararg{Any,N} where N), :(Base.#print_to_string#245), :($(QuoteNode(nothing))), :($(QuoteNode(Base.print_to_string))), :((Main.getfield)(obj, 1)::Int64), :((Main.getfield)(obj, 2)::Int64), :((Main.getfield)(obj, 3)::String), :((Main.getfield)(obj, 4)::Int64)))::String
      # meta: pop location
  end::String

?

4 Likes
julia> @generated function stringify(x::T) where {T}
           return :(string($([:(getfield(x, $name)) for name in fieldnames(T)]...)))
       end
stringify (generic function with 1 method)

julia> struct Foo
           a
           b
       end

julia> stringify(Foo("hello ", "bramtayl"))
"hello bramtayl"

EDIT: @yuyichao was quick on the draw and beat me to it :stuck_out_tongue:

Isn’t there some sort of taboo on generated functions?

1 Like

Taboo? They are quite easy to misuse but for this case it seems like a perfect tool.