This note is about the use of a Type as a supertype when defining a struct.
Let’s select Integer to be our exemplar supertype.
Here is very simple struct that is using Integer as its supertype.
struct BehavesLikeAnInteger <: Integer
value::Int
end
intlike = BehavesLikeAnInteger(5)
# BehavesLikeAnInteger(5)
Here is almost the same thing – now gone horribly wrong.
struct BehavesLikeAnInteger <: Integer
value::String
end
notatall_intlike = BehavesLikeAnInteger("The marmalade is talking.")
# BehavesLikeAnInteger("The marmalade is talking.")
The Type that is used as the supertype for a struct is how we say:
“This struct of mine embodies the intent that Type evinces/evokes.
Moreover, the information that I keep each time that this struct
is constructed, provides the wherewithall to participate in operations
that expect some kind of [are designed to accept an] Integer.”
So the choice of a supertype is not bringing to bear the operational
functionality of the type, rather, it is bringing your struct into
the operational fold of that supertype. It is incumbent upon the
designer of a struct with supertype to provide the functionality
necessary for the struct “to play well with” extant methods.
Often, this is accomplished by forwarding the method through e.g.
the value field of a struct. Here is an example.
struct BehavesLikeAnInteger <: Integer
value::Int
end
# iszero(x) returns a Bool, no re-construction is needed
Base.iszero(x::BehavesLikeAnInteger) =
iszero(x.value)
# abs(x) returns a typeof(x), re-construction is needed
Base.abs(x::BehavesLikeAnInteger) =
BehavesLikeAnInteger(abs(x.value))
This gets tedious very quickly. I wrote TypedDelegation.jl
for just this reason. That package makes type respectful
delegation through the field[s] of a struct easy to express.
There are examples of how it is used in the README.md file.
Another reason to choose a supertype for your struct is that
the supertype is available for use in guiding multidispatch.
abstract type VideoGame end
abstract type SinglePlayerGame <: VideoGame end
abstract type MultiPlayerGame <: VideoGame end
struct Pacman <: SinglePlayerGame
<...>
player::GamePlayer
end
struct RedDeadRedemption2 <: MultiplayerGame
<...>
players::Vector{GamePlayer}
end
function gameplayer(game::SinglePlayerGame, player::GamePlayer) ... end
function gameplayers(game::MultiPlayerGame, players::Vector{GamePlayer}) ... end
Register all your players, in one [actually two] fell swoop,
independent of the specific game being played, dispatched by
the virtual trait isita_singleplayergame implemented through
inheritance and dispatch.