BalenCPP
BalenCPP

Reputation: 301

Why can't I specify the type of the names, in a named tuple, as a tuple of symbols in Julia?

There's this function:

function Γ(state::Dict{Symbol, <:Tuple{Vararg{Symbol}}})::Vector{NamedTuple{<:Tuple{Vararg{Symbol}}, <:Tuple{Vararg{Symbol}}}}
    kwargs = Dict(kwargs)
    keys = [key for (key, value) ∈ state]
    instances_vec = [value for (key, value) ∈ state]
    data = Vector{Vector{Symbol}}()
    function recursive(data, current, depth = 1)
        if depth <= length(instances_vec)
            for instance ∈ instances_vec[depth]
                recursive(data, push!(copy(current), instance), depth + 1)
            end
        else
            push!(data, current)
        end
    end
    recursive(data, [])
    return [(;zip(keys,configuration)...) for configuration ∈ data]
end

which when run like this:

identifiers = Dict(:Id=>(:One, :Two, :Three, :Four, :Five), :Nationality=>(:Brit, :German), :Color=>(:Red, :Blue, :Green))

Γ(identifiers)

gives the following error:

ERROR: MethodError: Cannot `convert` an object of type
NamedTuple{(:Id, :Nationality, :Color),Tuple{Symbol,Symbol,Symbol}} to an object of type
NamedTuple{#s589,#s588} where #s588<:Tuple{Vararg{Symbol,N} where N} where #s589<:Tuple{Vararg{Symbol,N} where N}

The type of the returned is: Array{NamedTuple{(:Id, :Nationality, :Color),Tuple{Symbol,Symbol,Symbol}},1} and the type of (:Id, :Nationality, :Color) is Tuple{Symbol,Symbol,Symbol} so I don't see why this gives a conversion error when there is no need for conversion especially since:

typeof((:Id, :Nationality, :Color)) <: Tuple{Vararg{Symbol}} && Tuple{Symbol,Symbol,Symbol} <: Tuple{Vararg{Symbol}} = true

Upvotes: 4

Views: 349

Answers (1)

First, the conversion comes from the fact that the declaration of your function declares a return type. If you remove (or comment out) this type annotation

function Γ(state::Dict{Symbol, <:Tuple{Vararg{Symbol}}}) #::Vector{NamedTuple{<:Tuple{Vararg{Symbol}}, <:Tuple{Vararg{Symbol}}}}
    kwargs = Dict() # <- there was a typo here in your minimal example
    keys = [key for (key, value) ∈ state]
    instances_vec = [value for (key, value) ∈ state]
    data = Vector{Vector{Symbol}}()
    function recursive(data, current, depth = 1)
        if depth <= length(instances_vec)
            for instance ∈ instances_vec[depth]
                recursive(data, push!(copy(current), instance), depth + 1)
            end
        else
            push!(data, current)
        end
    end
    recursive(data, [])
    return [(;zip(keys,configuration)...) for configuration ∈ data]
end

then everything is fine:

julia> identifiers = Dict(:Id=>(:One, :Two, :Three, :Four, :Five), :Nationality=>(:Brit, :German), :Color=>(:Red, :Blue, :Green))
Dict{Symbol,Tuple{Symbol,Symbol,Vararg{Symbol,N} where N}} with 3 entries:
  :Id          => (:One, :Two, :Three, :Four, :Five)
  :Nationality => (:Brit, :German)
  :Color       => (:Red, :Blue, :Green)

julia> Γ(identifiers)
30-element Array{NamedTuple{(:Id, :Nationality, :Color),Tuple{Symbol,Symbol,Symbol}},1}:
 (Id = :One, Nationality = :Brit, Color = :Red)
 (Id = :One, Nationality = :Brit, Color = :Blue)
 (Id = :One, Nationality = :Brit, Color = :Green)
 [...]

Note that typing the arguments of a function/method where necessary is considered good practice. However, code that provides type assertions for return values is often less idiomatic in Julia: the compiler is really good at figuring out what the return type is!


Now the reason why this conversion fails is because the element type provided in the signature is not consistent with the actual element type of the returned vector:

julia> elem = (Id = :One, Nationality = :Brit, Color = :Red)
(Id = :One, Nationality = :Brit, Color = :Red)

julia> typeof(elem)
NamedTuple{(:Id, :Nationality, :Color),Tuple{Symbol,Symbol,Symbol}}

julia> elem isa NamedTuple{<:Tuple{Vararg{Symbol}}, <:Tuple{Vararg{Symbol}}}
false

For NamedTuple{A,B} to match NamedTuple{<:Tuple{Vararg{Symbol}}, <:Tuple{Vararg{Symbol}}}, both conditions below have to hold:

  • A <: Tuple{Vararg{Symbol}}
  • B <: Tuple{Vararg{Symbol}}

Here, we have:

A = (:Id, :Nationality, :Color)
B = Tuple{Symbol,Symbol,Symbol}

so that, like you said, the condition for B holds:

julia> B <: Tuple{Vararg{Symbol}}
true

but not the condition for A:

julia> A <: Tuple{Vararg{Symbol}}
ERROR: TypeError: in <:, expected Type, got Tuple{Symbol,Symbol,Symbol}
Stacktrace:
 [1] top-level scope at REPL[10]:1

       # A is a value of type Tuple{...}, not the type itself
julia> typeof(A) <: Tuple{Vararg{Symbol}}
true

Upvotes: 3

Related Questions