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?
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.
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.
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.
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.
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