# Is there a way to get a UnionAll base type from a concrete type?

**URL:** https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328
**Category:** General Usage
**Created:** [November 6, 2024, 10:56am UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328 "2024-11-06T10:56:10Z")
**Posts on this page:** 16
**Page:** 1

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [November 6, 2024, 10:56am UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/1 "2024-11-06T10:56:10Z")

</div>

I ran into a minor obstacle where I needed the UnionAll base type of a value, but I couldn’t figure out how to do it (without explicit `isa` checks). I.e.,

```julia-repl
julia> p = :a => 1
:a => 1

julia> typeof(p)
Pair{Symbol, Int64}

julia> # ? something to return Pair from Pair{Symbol, Int64}?

julia> p isa Pair ? Pair : error("need to handle all used types")
Pair

julia> supertype(typeof(p)) # not good, skips the UnionAll type
Any

```

It’s not a big deal, but I couldn’t find a generic Core or Base call for this purpose.

**Update** : the accepted solution below is more like a conclusion of the discussion rather than a solution, as it relies on Julia’s internal structures instead of public APIs, which could change in future implementations.

---

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [November 6, 2024, 11:08am UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/2 "2024-11-06T11:08:15Z")

</div>

Out of curiosity, what do you need that type for? There’s generally not much of anything meaningful you can actually do with that.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 6, 2024, 11:59am UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/3 "2024-11-06T11:59:54Z")

</div>

Seems like an XY problem:

[https://xyproblem.info](https://xyproblem.info)

Try taking a step back and describe your actual goals.

> [@HanD](#):
>
> I needed the UnionAll base type of a value

Doubtful.

> [@HanD](#):
>
> couldn’t find a generic Core or Base call for this purpose.

And that’s a good thing, such functionality wouldn’t be useful. It wouldn’t even be meaningful if the types aren’t hardcoded.

---

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [November 6, 2024, 12:29pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/4 "2024-11-06T12:29:01Z")

</div>

Allow me to disagree. It could be useful in certain contexts, but I’m perfectly okay with accepting no for an answer.

That being said, it is definitely not an XY problem. I never said it was a _problem_ in the first place, I merely called it a _minor obstacle_. I also explicitly stated that it _can_ be addressed with the use of the `isa` operator (among others). My question was triggered by my opinion that it would be _more elegant_ with a generic stdlib function, if such a function existed. However, there is no accounting for taste.

---

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [November 6, 2024, 12:32pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/5 "2024-11-06T12:32:11Z")

</div>

> [@Sukera](#):
>
> Out of curiosity, what do you need that type for? There’s generally not much of anything meaningful you can actually do with that.

I could/would use it as a key in a Dict, for example, in which I group objects of a type hierarchy based on their (unparameterized) types. Like I said, this can be achieved via other means, so no hard feelings.

---

<div class="post-metadata">

### Author: ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)
#### Post date: [November 6, 2024, 1:39pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/6 "2024-11-06T13:39:42Z")

</div>

You can use

```julia
julia> typeof(Pair(1,3)).name.wrapper
Pair

```

That being said, this is a very niche requirement and somewhat of a light smell that you’re about to do something ill-considered.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 6, 2024, 1:49pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/7 "2024-11-06T13:49:15Z")

</div>

> [@HanD](#):
>
> I could/would use it as a key in a Dict, for example, in which I group objects of a type hierarchy based on their (unparameterized) types.

The crux of the issue is that “unparameterized” is arbitrary and almost meaningless. If the set of types isn’t known upfront, that is - which conflicts with your request for a _generic_ solution. So it wouldn’t be possible to make use of such a function in generic code anyway.

It would of course be possible to implement such a function as you request, but its public interface would be ugly, and, IMO, most use of it would be misuse.

---

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [November 6, 2024, 2:43pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/8 "2024-11-06T14:43:01Z")

</div>

> [@foobar\_lv2](#):
>
> You can use
> 
> ```julia
> julia> typeof(Pair(1,3)).name.wrapper
> Pair
> 
> ```
> 
> That being said, this is a very niche requirement and somewhat of a light smell that you’re about to do something ill-considered.

Yeah, no, that would be ugly, and would break easily.

---

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [November 6, 2024, 2:56pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/9 "2024-11-06T14:56:57Z")

</div>

> [@nsajko](#):
>
> The crux of the issue is that “unparameterized” is arbitrary and almost meaningless.

Look, I really don’t want to get into an endless theoretical debate, but I think you are making bold statements quite easily here. Why would `Pair` be “almost meaningless”, as you put it? Quite, the contrary, it is perfectly meaningful.

Julia implementations often use type parameters in a complex type to avoid the use of `Any`, thus make it concrete and be able to optimize the code that uses it. Hence its not `struct Pair; a; b end`, even though _semantically_ that would work equally well. In many cases, however, the _user_ of the type doesn’t really care what the type parameters are, as long as they work. This is recognized and appreciated by the existence of constructors without explicit type parameters, i.e., that you can write `Pair(1, 2.0)`, and the parameters will be deduced for you. In other words, it _is supported_ to use a UnionAll to construct a concrete object, but there is no way back from the object to the UnionAll type. One _could_ argue that this is a reasonable decision, but it definitely results in an asymmetry, and not as obvious as you try to make it sound.

---

<div class="post-metadata">

### Author: ![JonasWickman](https://avatars.discourse-cdn.com/v4/letter/j/9de0a6/32.png) [@JonasWickman](https://discourse.julialang.org/u/JonasWickman)
#### Post date: [November 6, 2024, 3:45pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/10 "2024-11-06T15:45:55Z")

</div>

SparseDiffTools.jl (part of the SciML ecosystem) uses this construction, but it’s also based on `.wrapper`.

> <https://github.com/JuliaDiff/SparseDiffTools.jl/blob/55f0d5709f88d71b205ec255467ade14b6808119/src/SparseDiffTools.jl#L59>

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [November 6, 2024, 4:52pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/11 "2024-11-06T16:52:03Z")

</div>

> [@HanD](#):
>
> In other words, it _is supported_ to use a UnionAll to construct a concrete object, but there is no way back from the object to the UnionAll type.

Note that for both plain and UnionAll types, `TypeName(args...)` does **not** work generically to construct a concrete object. It works for some types, but by no means is a general mechanism.

If you want to construct an instance of a type from its field values, look at ConstructionBase.jl: it’s a small package providing the `constructorof(SomeType)(args...)` interface.

---

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [November 7, 2024, 7:46am UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/12 "2024-11-07T07:46:28Z")

</div>

> [@aplavin](#):
>
> Note that for both plain and UnionAll types, `TypeName(args...)` does **not** work generically to construct a concrete object. It works for some types, but by no means is a general mechanism.

True, it’s not a general mechanism, but it is definitely a well grounded convention. I was merely arguing that the notion of a reverse function is not that absurd.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 7, 2024, 10:22am UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/13 "2024-11-07T10:22:46Z")

</div>

> [@JonasWickman](#):
>
> `Base.@pure __parameterless_type(T) = Base.typename(T).wrapper`

This shouldn’t be marked as the solution, as that would mislead others into thinking the implementation is acceptable for general package code.

Could you at least edit in a warning about this being unsupported?

> [@HanD](#):
>
> it is definitely a well grounded convention

There’s no such convention.

---

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [November 7, 2024, 10:44am UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/14 "2024-11-07T10:44:52Z")

</div>

> [@nsajko](#):
>
> Could you at least edit in a warning about this being unsupported?

Done.

> [@nsajko](#):
>
> > [@HanD](#):
> >
> > it is definitely a well grounded convention
> 
> There’s no such convention.

Given the number of constructors out there which allow you to omit the type parameters, I would say there is a _de facto_ convention. But can we at least agree that it’s a very common _pattern_?

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [November 7, 2024, 11:21am UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/15 "2024-11-07T11:21:03Z")

</div>

The convention is to use the `const` name for the `UnionAll` to call a constructor method provided it was defined, not to compute it from a concrete parametric type first. The reason is that this only gets you the one `UnionAll`, what if you want `Pair{Symbol}` or `Pair{<:Any, Int64}` (perhaps with a `{}`-less alias)? That’s what method signatures and dispatch can do, though it’s as granular as `isa` checks.

That said, this is an unconventional type-processing utility, and I would prefer highlighting the very public fallback by ConstructionBase instead of the `.name.wrapper` approach:

```julia
@generated function constructorof(::Type{T}) where T
    getfield(parentmodule(T), nameof(T))
end

```

This was apparently around since v0.1.0, probably before the now-preferred `getglobal` was available. I don’t know why it’s `@generated`, this pattern is for fixing a method call despite method table changes, but `getfield` isn’t a multimethod and neither `parentmodule` nor `nameof` are intended to be extended.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [November 7, 2024, 2:02pm UTC](https://discourse.julialang.org/t/is-there-a-way-to-get-a-unionall-base-type-from-a-concrete-type/122328/16 "2024-11-07T14:02:19Z")

</div>

> [@Benny](#):
>
> I would prefer highlighting the very public fallback by ConstructionBase instead of the `.name.wrapper` approach:
> 
> ```julia
> @generated function constructorof(::Type{T}) where T
> getfield(parentmodule(T), nameof(T))
> end
> 
> ```

This may return (non-public) implementation details. E.g.:

```julia
module A
    module B
        struct Private{P} end
    end
    export Public
    const Public = B.Private{Int}
end

```

In the REPL:

```julia-repl
julia> module A
           module B
               struct Private{P} end
           end
           export Public
           const Public = B.Private{Int}
       end
Main.A

julia> using .A

julia> f(t::DataType) = getglobal(parentmodule(t), nameof(t))
f (generic function with 1 method)

julia> f(Public)
Main.A.B.Private

```

And it’ll throw if one, .e.g., passes it an `Union`. EDIT: a PR: [better error message when the `constructorof` fallback is passed a `Union` by nsajko · Pull Request #97 · JuliaObjects/ConstructionBase.jl · GitHub](https://github.com/JuliaObjects/ConstructionBase.jl/pull/97)

> [@Benny](#):
>
> I don’t know why it’s `@generated`,

Submitted a PR: [make the `constructorof` fallback method non-`@generated` by nsajko · Pull Request #98 · JuliaObjects/ConstructionBase.jl · GitHub](https://github.com/JuliaObjects/ConstructionBase.jl/pull/98)
