Error with .parameters in nested parametric types

bug

#1

I am trying to extract the Float64 from the type below, works in v0.5, breaks in v0.6. Apparently something to do with DataType vs UnionAll.

julia> t = Dict{String, Array{Float64}}
Dict{String,Array{Float64,N} where N}

julia> t.parameters[1]
String

julia> t.parameters[2]
Array{Float64,N} where N

julia> t.parameters[2].parameters[1]
ERROR: type UnionAll has no field parameters

#2

eltype works on types, too:

julia> eltype(Array{Float64,2})
Float64

#3

Right, that solves my problem, thanks. But I still think this behaviour is unintuitive, especially in the following completely artificial example.

julia> t = Array{Array{Dict{String, Array{Array{Float64,2}}},2},2}
Array{Array{Dict{String,Array{Array{Float64,2},N} where N},2},2}

julia> t.parameters[1]
Array{Dict{String,Array{Array{Float64,2},N} where N},2}

julia> t.parameters[1].parameters[1]
Dict{String,Array{Array{Float64,2},N} where N}

julia> t.parameters[1].parameters[1].parameters[2]
Array{Array{Float64,2},N} where N

julia> typeof(t.parameters[1].parameters[1])
DataType

julia> t.parameters[1].parameters[1].parameters[2].parameters[1]
ERROR: type UnionAll has no field parameters

julia> typeof(t.parameters[1].parameters[1].parameters[2])
UnionAll

Seems like when we get to an array of floating numbers or an array of arrays, it treats it a a UnionAll as opposed to DataType. If this is normal, I hope someone explains the rationale.


#4

The UnionAll comes from the where N part. It is literally the set of all types for which you could plug in something concrete for the N. See https://docs.julialang.org/en/latest/devdocs/types/#UnionAll-types-1 .

In general, try to extract type parameters with functions, creating them if they don’t exist. Eg you could define

myeltype(::Type{<: AbstractArray{T}}) where {T} = T

if it wasn’t in the language. Working with the type fields is a fallback, only use it when there is no other way.


#5

Array{Array{Float64, 2}, N} where N describes an entire set of types, that is, the set of all Array{Array{Float64, 2}, N} types with some unknown number of dimensions N. It doesn’t really make sense to access the parameters of an entire set of types, since one of those parameters is the unknown N.

Also, as a general rule, well-behaved Julia packages usually follow the convention that the API consists of the exported methods and types, but that users shouldn’t directly access the fields of those types (or if they do, they should expect those fields to change occasionally). Julia itself acts this way too: in general, if you’re calling methods from Base things should be more stable than if you’re digging around inside the fields of a Base type.


#6

It’s interesting that you find this behaviour intuitive. I see where you are coming from, but I view this behaviour as a kind of inconsistency myself. I think all types should be of the same meta-type, parametric or not, with where or without, it’s just nice to not have to worry about that when coding.

ETA: Actually I take that back! It seems there is a .body attribute which can be used to get the body of the UnionAll as so union_all.body.parameters where isa(union_all, UnionAll) == true.

ETA2: It’s interesting to see how Julia evolves and we just have to unlearn one rule and learn another at a rate I never experienced before with other more stable languages.


#7

You should not access the fields of a DataType, doing so is a hack. Yes, I have done so myself at times, but it is a hack. Also, those fields are not part of the public API and will change from version to version, as you have found out. (And I’m sure that there are very good reasons for the change which happened from 0.5 to 0.6.)

So, heed Tamas’ advice:

This is the Julian way of doing this, you should learn it.


#8

Also, this un-merged documentation goes a bit in this direction, it’s a good read: