Extract type name only from parametric type

Suppose I have a function that takes a value of a parametric type, for example:

abstract type AbstractMyType{Q} end
struct MyType1{Q} <: AbstractMyType{Q} end
struct MyType2{Q} <: AbstractMyType{Q} end

f(x::T) where {T<:AbstractMyType} = # something...

Inside f, I want to determine the type of x in a generic way, but without the parameters. For example, if f si called with x::MyType1{Int}, I want to have a variable inside f that holds the value MyType1.

For example one way to do it is to define methods:

f(x::T) where {T<:MyType1} = begin t = MyType1; #=more code... =# end
f(x::T) where {T<:MyType2} = begin t = MyType2; #=more code... =# end

But this gets repetitive if I have many derived types. Is there a better way?

One way using metaprogramming:

for T in (MyType1, MyType2)
    @eval getname(::Type{<:$T}) = $T
end
f(x::T) = begin t = getname(T); #=more code... =# end
1 Like

This assumes you know the list of derived types (MyType1, MyType2). Hopefully there is a generic way?

See the discussion in Stripping parameter from parametric types

This is probably bad practice but here is the relevant code from that post:

name(T::DataType) = T.name
name(T::UnionAll) = name(T.body)
1 Like

name returns a Core.TypeName. This is not the same as the type.

1 Like

Ya sorry missed that, name(::Type{T}) where T = eval(nameof(T)) also works but it is not as performant as my first solution, so you can call it a tradeoff.

Thanks that works. But the computation of T here is not occurring at compile time? Is this what you mean when you say this is not as performant?

eval is usually frowned upon in a function cuz ya it could be anything in there and it is evaluated in global scope so

f() = eval(:(a = 1))

actually defines a outside of f, so it is a bit tricky to reason about when inside a function and is a sign you are doing things in a non-Julian way.

1 Like

The type is not computed at compile-time.

thename(::Type{T}) where {T} = eval(nameof(T))
f(x::T) where {T} = thename(T)
@code_warntype f(2)

produces:

Body::Any
1 ─ %1 = (Base.getfield)(Int64, :name)::Core.TypeName
│ %2 = (Base.getfield)(%1, :name)::Symbol
│ %3 = invoke Main.eval(%2::Symbol)::Any
└── return %3

(I highlighted the Any).

This is a big turnoff.

Have you read the linked thread?

Why not:

mytype(::MyType1) = MyType1
mytype(::MyType2) = MyType2
# etc

f(x::AbstractMyType) = begin t = mytype(x); #=more code... =# end

Granted, it is one additional line per subtype, but not more.

2 Likes

Check out Jameson’s last comment on that thread I linked for the best way to do this “non-sensical” operation according to him.

1 Like
julia> struct Foo{T}
       x::T
       end

julia> Base.typename(Foo{Int64})
Foo

julia> VERSION
v"1.0.0"
2 Likes

What is wrong with handling the Core.TypeName in your use case?

This returns Core.TypeName, note a `Type.

I had defined methods that take a generic type parameter, such as MyType1, that don’t recognize Core.TypeName.

Ah, got it.

Just in case I was not clear, I meant to use .wrapper and adapt the code from Jameson’s comment as follows:

name(::Type{T}) where {T} = (isempty(T.parameters) ? T : T.name.wrapper)

Sorry should have been more clear.

1 Like

Do you mean this?

help?> Core.TypeName
  No documentation found.

  Summary
  ≡≡≡≡≡≡≡≡≡

  mutable struct Core.TypeName <: Any

  Fields
  ≡≡≡≡≡≡≡≡

  name        :: Symbol
  module      :: Module
  names       :: Core.SimpleVector
  wrapper     :: Type
  cache       :: Core.SimpleVector
  linearcache :: Core.SimpleVector
  hash        :: Int64
  mt          :: Any

julia> Base.typename(Vector{Int})
Array

julia> typeof(ans)
Core.TypeName

julia> Base.typename(Vector{Int}).wrapper
Array

julia> typeof(ans) 
UnionAll 

Core.TypeName.wrapper seems to be what you want.

2 Likes