How to avoid supplying exact types in a Dict constructor?

Hi,

When I have a type hierarchy where TC is a concrete implementation of type T and I have defined a struct as follows:

struct Foo
    d::Dict{T,Any}
end

What do I need to do to be able to call the constructor as follows:

Foo(Dict(TC() => value))

Instead of having to call it as:

Foo(Dict{T, Any}(TC() => value))

And what is the logic behind it?

Thanks in advance,
Stef

Nothing?

julia> abstract type T end

julia> struct TC <: T end

julia> struct Foo
           d::Dict{T,Any}
       end

julia> Foo(Dict(TC() => 1))
Foo(Dict{T,Any}(TC() => 1))

Maybe you have a full MWE that illustrates your point?

2 Likes

Hi,

@kristoffer.carlsson well, that’s what I thought too. Here’s the source code:

abstract type Blueprint end

struct ConsumableBlueprint <: Blueprint
    type_id::UUID
    name::String
    ConsumableBlueprint(name::String) = new(uuid4(), name)
end

struct ProducerBlueprint <: Blueprint
    type_id::UUID
    name::String
    lifecycle::Restorable
    restore_res::Dict{Blueprint,Int64}
    restore::Float64
    batch_req::Dict{Blueprint,Int64} # Required input per batch
    batch::Dict{Blueprint,Int64} # batch per batch. The Blueprint and the number of items per blueprint.
    max_production::Int64 # Max number of batches per production cycle

    ProducerBlueprint(
        name::String,
        lifecycle::Restorable = Restorable();
        restore_res::Dict{Blueprint,Int64} = Dict{Blueprint,Int64}(),
        restore::Real = 0,
        batch_req::Dict{Blueprint,Int64} = Dict{Blueprint,Int64}(),
        batch::Dict{Blueprint,Int64} = Dict{Blueprint,Int64}(),
        max_production::Int64 = INF
    ) = new(uuid4(), name, lifecycle, restore_res, restore, batch_req, batch, max_production)
end

When I execute the following:

labour_bp = ConsumableBlueprint("Labour")
food_bp = ConsumableBlueprint("Food")
factory_bp = ProducerBlueprint(
        "Factory",
        batch_req = Dict(labour_bp => 2),
        batch = Dict(food_bp => 1)
    )

I get this error:

ERROR: TypeError: in keyword argument batch_req, expected Dict{Blueprint,Int64}, got a value of type Dict{ConsumableBlueprint,Int64}
Stacktrace:
 [1] top-level scope at none:1

Kind regards,
Stef

Again, please provide an MWE (emphasis on the W (working)). Copy pasting this I get

ERROR: UndefVarError: Restorable not defined
2 Likes

In your example, when you try to build factory_bp you set a field with batch_req = Dict(labour_bp => 2), but labour_bp at that time is concretely typed as ConsumableBlueprint, so the resulting Dict is not a Dict{Blueprint,Int64} it’s a Dict{ConsumableBlueprint,Int64}. You have at least a few choices depending on how it’s going to be used.

  1. If ProduceBlueprint is only going to read from the Dict, then its fields could be changed to Dict{<:Blueprint,Int64} so that this kind of input is allowed. This would require changing the fields of the struct and of its constructor.
  2. If ProduceBlueprint needs to be able to write arbitrary blueprints to the Dict objects it receives, then you need batch_req = Dict{Blueprint,Int64}(labour_bp => 2)
2 Likes

Just for the sake of providing options:

    1. Add an outer constructor for which restore_res, restore, and batch are untyped and convert(Dict{Blueprint, Int64}, batch) them before passing to the inner constructor

@kristoffer.carlsson the full code is available on GitHub. The code in the following file:

https://github.com/lpu-howest/loreco-abm/blob/main/cockpit/start_cockpit.jl

initialises all structs.

The unit test I’m trying to run is the “Producer” test set near the bottom of the following file:
https://github.com/lpu-howest/loreco-abm/blob/main/cockpit/production/unit_test.jl

Kind regards,
Stef

@malacroi, I went for the first solution since ProducerBlueprint doesn’t need to write anything to the Dict.

So I thought I could do something similar for Entities:

struct Entities
    entities::Dict{Blueprint,Vector{Entity}}
    Entities() = new(Dict{Blueprint,Vector{Entity}}())
end

function Entities(resources::Dict{<:Blueprint,Vector{<:Entity}})
    entities = Entities()

    for blueprint in keys(resources)
        for entity in resources[blueprint]
            push!(entities, entity)
        end
    end

    return entities
end

Base.keys(entities::Entities) = keys(entities.entities)
Base.values(entities::Entities) = values(entities.entities)
Base.getindex(entities::Entities, index::Blueprint) = entities.entities[index]
Base.setindex!(entities::Entities, e::Array{Entity,1}, index::Blueprint) = (entities.entities[index] = e)

function Base.push!(entities::Entities, entity::Entity)
    if entity.blueprint in keys(entities)
        push!(entities[entity.blueprint], entity)
    else
        entities[entity.blueprint] = Vector{Entity}([entity])
    end

    return entities
end

function Base.pop!(entities::Entities, blueprint::Blueprint)
    if blueprint in keys(entities)
        e = pop!(entities[blueprint])

        if length(entities[blueprint]) == 0
            pop!(entities.entities, blueprint)
        end

        return e
    else
        return nothing
    end
end

But when I call the second constructor with:

labour_bp = ConsumableBlueprint("Labour")
Entities(Dict(labour_bp => [Consumable(labour_bp), Consumable(labour_bp)]))

where Consumable <: Entity, I get the following error:

ERROR: MethodError: no method matching Entities(::Dict{ConsumableBlueprint,Array{Consumable,1}})
Closest candidates are:
  Entities() at /Users/stef/Programming/Julia Projects/loreco-abm/cockpit/production/entities.jl:76
  Entities(::Dict{var"#s50",Array{var"#s49",1} where var"#s49"<:Entity} where var"#s50"<:Blueprint) at /Users/stef/Programming/Julia Projects/loreco-abm/cockpit/production/entities.jl:79
Stacktrace:
 [1] top-level scope at none:1

var"#s50" is labour_bp which is <: Blueprint and var"#s49" are the 2 Consumables which are both <: Entity

How come the same trick doesn’t work here?

Thanks in advance,
Stef

Since you specified an inner constructor Entities() with no arguments, it cannot take a Dict.

Perhaps you meant an outer constructor?

@Tamas_Papp That’s the thing, I did define an outer constructor which takes a Dict:

function Entities(resources::Dict{<:Blueprint,Vector{<:Entity}})
    entities = Entities()

    for blueprint in keys(resources)
        for entity in resources[blueprint]
            push!(entities, entity)
        end
    end

    return entities
end

Unless I’m mistaken somehow.

Your MWE does not run as Entity is not defined. It is hard to help without a self-contained MWE, see

My apologies for providing an incomplete MWE.

Here is an executable one.

abstract type Blueprint end

struct ABlueprint <: Blueprint
    val::Int64
    ABlueprint(val::Int64=0) = new(val)
end

abstract type Entity end

struct AnEntity <: Entity
    val::Int64
    AnEntity(val::Int64=0) = new(val)
end

struct Entities
    entities::Dict{Blueprint,Vector{Entity}}
    Entities() = new(Dict{Blueprint,Vector{Entity}}())
end

function Entities(resources::Dict{<:Blueprint,Vector{<:Entity}})
    entities = Entities()

    for blueprint in keys(resources)
        for entity in resources[blueprint]
            push!(entities, entity)
        end
    end

    return entities
end

Entities(Dict(ABlueprint() => [AnEntity()]))

The last line results in the following error:

ERROR: MethodError: no method matching Entities(::Dict{ABlueprint,Array{AnEntity,1}})
Closest candidates are:
  Entities() at none:3
  Entities(::Dict{var"#s10",Array{var"#s9",1} where var"#s9"<:Entity} where var"#s10"<:Blueprint) at none:1
Stacktrace:
 [1] top-level scope at none:1

When calling the methods() function I get the following output:

 methods(Entities)
# 2 methods for type constructor:
[1] Entities() in Main at none:3
[2] Entities(resources::Dict{var"#s10",Array{var"#s9",1} where var"#s9"<:Entity} where var"#s10"<:Blueprint) in Main at none:1

My assumption was that I could therefore call the outer constructor of Entities() with a Dict that contains implementations of Blueprint and Entity.

Thanks in advance,
Stef

If you insist on this type signature, use

function Entities(resources::Dict{S,Vector{T}}) where {S <: Blueprint, T <: Entity}
    ...

See invariance.

That said, since you are traversing the input anyway, I think it is better to just allow all kinds of arguments that support pairs and use that interface.

1 Like