Extending the discussion in the review section of #159, we should identify the best method for constructing AbstractAgent types.
Arguments so far:
UnionAll types should be avoided as they cause a performance lossPossible place of misunderstanding:
When implementing #159 I was testing against simple types such as Agent3 from the tests,
mutable struct Agent3 <: AbstractAgent
id::Int
pos::Tuple{Int,Int}
weight::Float64
end
and the Cell type in the CA modules.
mutable struct Cell{T<:Integer, Y<:AbstractString} <: AbstractAgent
id::T
pos::Tuple{T, T}
status::Y
end
The point we were discussing had to do with my checks against something to this effect:
space = GridSpace((10,10))
model1 = ABM(Agent3, space)
model2 = ABM(Cell, space)
This, I believe is where the confusion lies, and the abstract type discussions clear up.
mutable struct Cell{T<:Integer, Y<:AbstractString} <: AbstractAgent is 'more' correct than mutable struct Agent3 <: AbstractAgent and should be more performant.model2 = ABM(Cell, space) should throw an errormodel2 = ABM(Cell{Int64, String}, space) should be the correct and suggested method.Please feel free to offer other interpretations.
This discussion is trivial once a thorough understanding of the type system is obtained. This is where: https://docs.julialang.org/en/v1/manual/types/
Your statement 1 is not valid in general. It depends on what Agent3 is. Is it concrete or not?
this:
mutable struct Agent3
id::Int
end
and then Agent3 is a concrete type. Cell{Int64, String} is also a concrete type (BTW never use Int64, ALWAYS use Int in its place). Cell is NOT a concrete type in this discussion.
Then, Dict{A, B} is ONLY a concrete type if BOTH A and B are concrete.
I can also strongly recommend my tutorial here: https://youtu.be/Fi7Pf2NveH0?t=3442
Thanks for the clarifications. This is my misunderstanding. I'll take a look at this again with new eyes an see if there's not something better we can do with the guards and example types.
This now shows how truly important it is to provide a constructor for ABM that takes an agent instance as an input. We did yet another mistake in #159 to not implement it.
Not everyone is deeply familiar with the type system, and in all honesty Agent based modelling doesn't really require deep programming familiarity in general; all the more reason to not study the type system. I expect many users of Agents.jl to not know this subtle, yet crucial difference.
Therefore we always throw errors at non-concrete A, and in the error message we say "if you are not sure, just pass an agent type instance".
Just for further clarity, I'm struggling with your statement about position 1. not being true in general.
There's a discussion in the docs saying (what I interpret to be) the opposite:
julia> mutable struct MyType{T<:AbstractFloat}
a::T
endThis is a better choice than
julia> mutable struct MyStillAmbiguousType
a::AbstractFloat
endbecause the first version specifies the type of a from the type of the wrapper object.
Can you elaborate on that a little?
AbstractFloat is not a concrete type. I discuss all these things in the video, and therefore don't want to spend too much time here repeating. In short
struct Agent
a::A
b::B
c::C
is a concrete type only if A, B, C are also concrete. There is this StillAMbiguousType is ambiguous is because its field a is not a concrete type. Notice on the other hand that
struct Agent{A, B, C}
a::A
b::B
c::C
is a parametric type on A, B, C. It is not the same. Here, Agent by itself will NEVER be a concrete type, no matter what you do. you need to specify A, B, C.
For the rest, please see the video.
If you want the same type of structure to have various different possibilities for the types of its fields, _then_ you use parametric types.
BTW, I haven't said it anywhere yet, but this is a good opportunity: I find it useless to use parametric types in our test suites and examples. The parametricity serves no purpose and gives no benefit. It only makes the code more convoluted.
The most often case of using parametric types is when you are _developing_ something. And indeed, our struct AgentBasedModel is parametrically typed since we _develop_ it for someone else, and don't know in advance the type of Agents they will create. But the Agent type doesn't have to be parametric. The user creates it, and I bet big dollar that 99,99% of cases, as a user you know what will be the field types of your agents.
@kavir1698
Ah, yes I see. Thanks (I'm watching your video in parallel).
Great. I'll look into altering the error messages as discussed already and add the second AgentBasedModel(agent::AbstractAgent) constructor.