I think all that can be simplified a bit, if every type that uses Person and wishes to use it’s functions, simply had a person function.
Try the following:
abstract type AbstractPerson end
# basic methods to define: name,age,set_age
#basic AbstractPerson
mutable struct Person <: AbstractPerson
name::String
age::Int
end
person(a::Person) = a
# CONSTRUCTOR
Person(name) = Person(name, 0)
#basic methods
name(a::AbstractPerson) = person(a).name
age(a::AbstractPerson) = person(a).age
set_age(a::AbstractPerson,x::Integer) = (person(a).age = x; x)
# TYPE METHODS: always use `AbstractPerson` as input type...
Base.display(p::AbstractPerson) = println("Person: ", name(p), " (age: ", age(p), ")")
function happybirthday(p::AbstractPerson)
set_age(p, age(p) + 1)
println(name(p), " is now ", age(p), " year(s) old")
end
call(p::AbstractPerson) = (print_with_color(:red, uppercase(name(p)), "!"); println())
#---------------------------------------------------------------------
# DERIVED TYPE : Citizen
# Use abstract type for the interface name, by convention prepend
# `Abstract` to the type name.
abstract type AbstractCitizen <: AbstractPerson end
# here you should think of basic methods for a AbstractCitizen,
# such as nationality(c::AbstractCitizen).
# TYPE MEMBERS (composition of `Person` fields and new ones)
mutable struct Citizen <: AbstractCitizen
person::Person
nationality::String # new field (not present in Person)
end
person(c::Citizen) = c.person
#basic abstractcitizen method
nationality(c::Citizen) = c.nationality
#Now everything defined for AbstractPerson should work for Citizen
#And you are not tied to field names anymore:
struct EternalBeing <: AbstractPerson end
name(e::EternalBeing) = "The One who Is"
age(e::EternalBeing) = typemax(Int)
set_age(e::EternalBeing, x::Integer) = nothing
const eternal = EternalBeing()
# All just work
display(eternal)
happybirthday(eternal)
call(eternal)
println()
zulima = Citizen(Person("Zulima Martín García", 44), "Spain")
display(zulima)
happybirthday(zulima)
call(zulima)