I want to be able to manipulate parametric types for example to replace some type parameter with a different one. Since the name field of a DataType contains a TypeName and not the type itself and since it does not seem to be allowed to have variables in the place of a type when stating parametric type expressions I currently have a bit of an inflexible kludge of code for this. Is there some simpler, more natural, Julian way of achieving this:
function replaceparam(t, pi, st)
newparams = collect(t.parameters)
newparams[pi] = st
newparams
end
# Not clear how we can get the parametric type from a composite one so we cheat
# by mapping to constructor functions from the type names.
TypeNameToReplaceFunction = Dict(
Array.name => (t, i, st) -> Array{replaceparam(t, i, st)...},
Dict.name => (t, i, st) -> Dict{replaceparam(t, i, st)...},
)
# Now we can create a new type from an existing one like for example:
t = Vector{String}
TypeNameToReplaceFunction[t.name](t, 1, AbstractString) # => Vector{AbstractString}
t2 = Dict{Int, Float64}
TypeNameToReplaceFunction[t2.name](t2, 2, String) # => Dict{Int64, String}
Any advice/hints on doing this in a more flexible, better way?
There’s no official exported way to do these things, but I’ve got a bunch of unexported but maintained functions in QuickTypes.jl that may help. For instance
julia> using QuickTypes: roottype, type_parameters
julia> int2float_types(t) = roottype(t){(x===Int ? Float64 : x for x in type_parameters(t))...}
int2float_types (generic function with 1 method)
julia> int2float_types(Array{Int, 3})
Array{Float64,3}
julia> int2float_types(Tuple{String, Int})
Tuple{String,Float64}
Does that answer your question? I’m not 100% sure I understood what you’re trying to do.
Yes, thanks, indirectly it does. The primary
field (on TypeName) is what I was missing. With that I can solve it. Big thanks!
primary
is gone in 0.6, so make a note to use the Compat
version when you upgrade.
The official, exported way to do this is to use similar
.
While you can manipulate the type directly as done in QuickTypes, in general this is unsound (e.g. you must strongly avoid doing this in an @generated
or @pure
function). This will be mentioned in the manual in the next release https://docs.julialang.org/en/latest/manual/methods/#Building-a-similar-type-with-a-different-type-parameter-1, under the section “Design Patterns with Parametric Methods”.
Thanks Jameson.
However, in my case I don’t want to convert any concrete data from one type to another, I need to actually find a way to dynamically apply one from a set of functions that has been defined for example for AbstractArray{Any,1} to an object of type Array{String,1}. By “going up” the normal type heirarchy with supertype I will traverse the “chain”: Array{String,1}, DenseArray{String,1}, AbstractArray{String,1}, Any. None of these will match the AbstractArray{Any,1} functions that could be applied in my case. Thus I want to manipulate the original Array{String,1} type and rather insert the types in String’s supertype “chain” (String, AbstractString, Any) into Array{X,1}. This will help me find the applicable AbstractArray{Any,1} functions in my case. The only way I have found so far is to manipulate the types in the style of QuickTypes.
Sorry if this is too terse to make any sense. If you have any ideas/inputs that is appreciated.
> typeintersect((Array{T, 1} where T >: String), AbstractArray{Any, 1})
Array{Any, 1}
1 Like