Accessing an object's unparameterized type

I have a function that receives an object with parameters attached to it, and I’d like it to call the object’s constructor with those parameters removed.

For example, consider:

struct Test{A, B} end

obj = Test{1,2}()

Suppose I write a function that receives obj; I’d like to call its constructor while discarding its parameters, in order to call it with different parameters, e.g. Test{3,4}().

I can access a representation of Test as a Core.TypeName:

typeof(obj).name == Base.typename(Test)

but I don’t know how to retrieve a callable Test constructor from it.

julia> Type((Test{1,2}).name)
ERROR: MethodError: no method matching Type(::Core.TypeName)

Knowing that types and their parameters can be teased out in the function definition, I’ve also given this a shot, but it just results in errors:

julia> (T where {T}){1,2}
ERROR: TypeError: in Type{...} expression, expected UnionAll, got Type{Any}

julia> T{1,2} where {T}
ERROR: TypeError: in Type{...} expression, expected UnionAll, got a value of type TypeVar

Any ideas on how to access the unparameterized constructor?

Thanks~

Maybe define the input of the function as

f(T{A,B}) where {T,A,B} = ...

then you can use T?

Julia does not support higher-kinded type parameters.

Given t::DataType, you might need getfield(t.name.module, t.name.name).

This has been discussed numerous times - search for “strip type parameters”.

This is the most relevant result:

There may not be such a constructor. This is not safe to do in general. Reflection can only work in limited cases here. The “canonical” solution is to have similar implemented on your type.

1 Like

Thanks, that was it! I had failed to explore the TypeName object’s properties.

Short answer:

struct Test{A,B} end
obj = Test{1,2}()

typeof(obj).name.wrapper{3,4}() # == Test{3,4}()

Or in a method definition,

funcname(obj::T) where T = begin
    T.name.wrapper{3,4}()
end
funcname(obj)

Be aware that this is not safe to do - TypeName is an internal object and may vanish at any point, or its fields may change name or vanish. This is not stable & supported API.

What are you ultimately trying to achieve with this? There’s probably a better/more stable way to do this.

1 Like

There is a way to do this without touching the internals:

Specifically:

getfield(parentmodule(T), nameof(T))

But notice its in a generated function for performance in ConstructionBase.jl, so also not a great option. name.wrapper has been working fine for quite some years now too.

1 Like

Wow, this library is a treasure trove of clever magic! Thanks!

I’m also currently accessing the type parameters with

T.parameters[1]
T.parameters[2]

In the same spirit, I presume this is not considered official API…

As a Julian you will not like this, but I’ve put together a package for easy and informal prototype-based programming à la JavaScript’s Object model. It’s a toy project for now, just to tinker around and see what’s possible and what makes sense, but it really got me into the weeds of Julia’s type system.

I started typing out an essay about what I’m doing here, but it might be easier to point to the code. The code in question is on lines 110 and 117 of src/object.jl, where I find the prototype “parent” object’s type, strip off the parameters (which encode the object’s contained types), and call its constructor (which can be one of three types: Dynamic, Static, or Mutable) to create an inheritor “child” object which has similar characteristics.

Objects.jl: Dynamic, Static, and Mutable Objects with Composition, Prototype Inheritance, Method Membership, and Type Tagging for Julia

With your help and Raf’s help, I’m pretty happy with it so far. :pray:

I would rely on ConstructionBase.jl for these things wherever possible, a good fraction of the ecosystem supports it now. For parameters, instead of x.parameters[2] just use regular function dispatch with type parameters? getparam(::MyType{<:Any,B}) where B = B (or define it for type type if you need that). Write it for an abstract type with consistent params.

And don’t worry, as “Julians” many of us have gone through phases like the one you are in. I wrote Mixers.jl in my first few months out of frustration with lack of field inheritance. Now I never use, and actively recommend against it.

Mostly we are guessing you will follow a similar arc with your need for Javascript-like objects :wink:

3 Likes

Ah right, I wrote this part of the code before I figured out how to do that, a few days ago. Hah, how time flies. :sweat_smile:

Hah that’s a relief! I actually looked at that package not so long ago and was wondering if I should be structuring my code with it. :sweat_smile:

We shall see :wink:

Now lines 85 and 92