Manipulating parametric types


#1

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?


#2

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.


#3

Yes, thanks, indirectly it does. The primary field (on TypeName) is what I was missing. With that I can solve it. Big thanks!


#4

primary is gone in 0.6, so make a note to use the Compat version when you upgrade.


#5

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


#6

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.


#7
> typeintersect((Array{T, 1} where T >: String), AbstractArray{Any, 1})
Array{Any, 1}