Subtyping type parameterization does not work in `@multiagent`.
Closed this issue · 13 comments
MWE:
using Agents
@multiagent struct MultiSchelling{X<:Real}(GridAgent{2})
@agent struct Civilian # can't re-define existing `Schelling` name
mood::Bool = false
group::Int
end
@agent struct Governor{X<:Real} # can't redefine existing `Politician` name
group::Int
influence::X
end
end
erros with
ERROR: TypeError: in <:, expected Type, got a value of type TypeVar
Stacktrace:
[1] top-level scope
@ C:\Users\datse\.julia\dev\Agents\src\core\agents.jl:312
By the way, I find it odd that one must specify the type X
in both the super type and the subtype. Can't the subtypes remain just a pure name ?
seems like a problem which should be addressed inside MixedStructTypes.jl
(don't really like assignments, but I will solve this soon anyway :D)
@Tortar before solving anything, can we first please consider a syntax simplification of the macro? Instead of
@multiagent struct MultiSchelling{X<:Real}(GridAgent{2})
@agent struct Civilian # can't re-define existing `Schelling` name
mood::Bool = false
group::Int
end
@agent struct Governor{X<:Real} # can't redefine existing `Politician` name
group::Int
influence::X
end
end
we could have
@multiagent struct MultiSchelling{X<:Real}(GridAgent{2})
@subagent Civilian # can't re-define existing `Schelling` name
mood::Bool = false
group::Int
end
@subagent Governor # can't redefine existing `Politician` name
group::Int
influence::X
end
end
two key differences: first, the subtypes do not need @agent struct
, instead they get only @subagent
. Only the supertype gets the struct
to indicate more that this is the only actiual struct
being created. Second, the subtypes do not get type parameterizations. That to me seems like a way to make the user make mistakes. There is no reason to duplicate the type parametetrization information, it should only be in the super type.
yes, I like it, but actually there is a reason, the subtypes are not functions, they are types themselves which accept constructor as Governor{Int}(...)
, if they would be just function it would have been impossible to have those constructors. But I don't think we need these explicit constructors in Agents.jl, and we can just live with Governor(1,...)
. Regardless, though, I think it is good to keep those constructors in the downstream package https://github.com/JuliaDynamics/MixedStructTypes.jl so if we want to go into this we should nonetheless reconstruct the parameter, in the sense that this
@multiagent struct MultiSchelling{X<:Real}(GridAgent{2})
@subagent Civilian
mood::Bool = false
group::Int
end
@subagent Governor
group::Int
influence::X
end
end
should become
@compact_struct_type MultiSchelling{X<:Real} begin
mutable struct Civilian
# fields of gridagent
mood::Bool = false
group::Int
end
mutable struct Governor{X} # -> again X here
# fields of gridagent
group::Int
influence::X
end
end
Seems not too difficult to check the presence of a parameter inside each subagent, so this should be doable
But I don't think we need these explicit constructors in Agents.jl, and we can just live with
Governor(1,...)
.
Yes, I agree.
Why should we keep {X}
inside the subtype? In the new tutorial I am telling the users "think of these subtypes as just functions that initialize the main type". The details of whether they actually are types or not are perhaps not useful for the end user.
No, I mean that it is fine not to have it in Agents.jl but I would really keep it in MixedStructTypes.jl which does the heavy lifting and so we need to reconstruct it anyway in the multiagent macro, but this is an implementation detail
Also actually we need to add a begin
@multiagent struct MultiSchelling{X<:Real}(GridAgent{2})
@subagent Civilian begin
mood::Bool = false
group::Int
end
@subagent Governor begin
group::Int
influence::X
end
end
but this seems anyway better
Actually , thinking what you said that Governor{X} is possible, perhaps we should leave the {X} there as well...?
yeah, I was in favor of changing this, because has some advantages stemming from the fact that we could really use function and not types for those names, which has some other consequences, but in reality after thinking about this I think that keeping the way it is now is better. So we are just left with the @subagent
thing, I think that we agree that this change is an improvement instead, so in the end:
@multiagent struct MultiSchelling{X<:Real}(GridAgent{2})
@subagent Civilian begin
mood::Bool = false
group::Int
end
@subagent Governor{X} begin
group::Int
influence::X
end
end
okay, and what would happen if a usrer wrote Governor{X<:Real}
when defining the macro? Like in #967 ?
Actually, now that I think about it, I am more in favor of not having {X}
in the subagents and truly making the subagent names just functions. So the macro literally creates two functions:
function Governor(id, pos, group, influence)
return MultiSchelling( ...)
end
function Governonr(; id, pos, group = 1, influence = 1)
return MultiSChellng(...)
end
and we instruct the users to simply not put type annotations when calling the types. Generally speaking, there shouldn't be any reason to do this, to differentiate via the type parameterization. Right? Or can we imagine a scenario where this is important?
Tried to imagine something, it is not clear to me when this would be really needed, but in any case, even if we don't mention in the expression of the macro, it will be there anyway, at least for some time, just undocumented, and in general it won't never be really as simple as those functions you wrote, but I think that we don't need to specify everything in details to users
ok I actually understood when we really need to specify parameters e.g. with a normal struct
julia> struct A{T}
x::Union{Int, T}
end
julia> A(1)
ERROR: UndefVarError: `T` not defined
Stacktrace:
[1] A(x::Int64)
@ Main ./REPL[1]:2
[2] top-level scope
@ REPL[2]:1
julia> A{Int}(1)
A{Int64}(1)
same happen with MixedStructTypes.jl types
so the only thing we can really do is changing @agent
in @subagent