A battle simulator needs actual archers, pikemen, and knights—not just a label for all warriors. In Julia, Archer, Pikeman, and Knight can be concrete types: you can create objects of each. Warrior can be an abstract type that groups them, but you cannot create a Warrior object. section 7.3 section 7.3.1
Listing 7.5 Definition of types in battle simulator
abstract type Warrior end #1
mutable struct Archer <: Warrior #2
name::String
health::Int
arrows::Int
end
mutable struct Pikeman <: Warrior
name::String
health::Int
end
mutable struct Knight <: Warrior
name::String
health::Int
mounted::Bool #3
end
These definitions put three concrete types under the abstract type Warrior. Each mutable struct declares the fields its objects store. section 7.3.1
A composite type is a concrete type with fields; a primitive type is concrete and has a single value. Abstract types have no fields. Although each warrior above has a name and health, Warrior does not enforce those fields—that shared shape is a convention. section 7.3.1
julia> robin = Archer("Robin Hood", 34, 24)
Archer("Robin Hood", 34, 24)
julia> Warrior()
ERROR: MethodError: no constructors have been defined for Warrior
The REPL contrast is the key test: constructing an Archer works; calling Warrior() fails because an abstract type has no constructor. section 7.3.1
Use <: to place a concrete type under an abstract type, as in Archer <: Warrior. A concrete type cannot be a subtype of another concrete type. In a type hierarchy, concrete types therefore occupy leaf nodes. section 7.3.1 section 7.5
That hierarchy is useful for behavior, not just organization. A battle!(a::Warrior, b::Warrior) method accepts different concrete warrior types. When it calls attack!, Julia selects the applicable method at runtime using the arguments’ types, even though the broad Warrior annotations do not specify which concrete combatants will arrive. section 7.4.2 section 7.5


