Public interface to get all type parameters?

Is it possible to implement the following function:

get_parameters(::Type{T}) where {T} = T.parameters

without relying on the internal field parameters? As far as I can tell, the answer seems to be no, since this approach keeps coming up as an answer to the question of how to get a type’s parameters, e. g.

and because of this old issue:

I might misunderstand the question, but would something like

julia> struct S{T1, T2} end

julia> get_parameters(::S{T1, T2}) where {T1, T2} = (T1, T2)
get_parameters (generic function with 1 method)

julia> s = S{Float32, UInt64}()
S{Float32, UInt64}()

julia> get_parameters(s)
(Float32, UInt64)

not work? (By the way, my suggestion here is very similar to a post of @sylvaticus in the first topic thread you linked.)

That requires you to implement get_parameters for every new type. Which is fine if you only need to extract the parameters from a particular type you know about. But it isn’t quite the same as a general reflection API.

On the other hand, extracting parameters of arbitrary types you know nothing about is of much more limited utility, unless you are writing some kind of generic debugging/introspection tool.

4 Likes

The key is really that you need to know something about the type itself in order to do anything meaningful with its parameters. And at that point, you might as well dispatch directly on the type (since you know it!).

What do you want to do with the parameters?

A decent alternative can be to expose the meaning you want in the parameters of an abstract supertype. The classic example here is AbstractVector{T}. I can’t expect that the first parameter of all arrays will be its eltype (simple counter-example: BitArray{1}). But I can dispatch on ::AbstractVector{T} and always know that the T there is the eltype.

5 Likes

If your use-case is interactive, where performance is not too important, and if you are happy with the textual representation in typeof, but want to capture the parameters in a Vector, you could parse this string representation using something like

function get_parameters(x)
    s = string(typeof(x))
    params = []
    bracket_counter = 0  # nested param level
    prev_param_end = nothing
    for (i, c) in enumerate(s)
        if c == '{'
            bracket_counter += 1
            isnothing(prev_param_end) && (prev_param_end = i)
        elseif c == '}'
            bracket_counter -= 1
        end
        if c == ',' && bracket_counter == 1 || c == '}' && bracket_counter == 0
            push!(params, eval(Meta.parse(s0[prev_param_end+1 : i-1])))
            prev_param_end = i
        end
    end
    return params
end

Note that this not particularly elegant, but it gets the job done (for certain use-cases, as explained at the start of the post).

julia> struct S{A, B, C} end

julia> s = S{UInt16, typeof(rand(2, 3)), 12}()
S{UInt16, Matrix{Float64}, 12}()

julia> get_parameters(s)
3-element Vector{Any}:
   UInt16
   Matrix{Float64} (alias for Array{Float64, 2})
 12

I appreciate all the answers trying to address potential use cases, but this was really a question about the language itself, so I’d be happy with a simple “no”. (Which seems to be the case?)

2 Likes

Just stumbled across this more recent issue:

A bit of a more specific subset of the older issue linked above, and more relevant to the question.