It happens quite regularly that different structs (EDIT: types) should share some attributes. If i got it right as a beginner: In Julia, you can extend abtract types, but they may not have any attributes. Concrete types (=structs) are not extendable. So, is there a way to avoid code repetition (for attributes name and weight) like in the given example?
abstract type GameObj end
struct Gem <: GameObj
name::String
weight::Int64
worth::Int64
end
struct Medicine <: GameObj
name::String
weight::Int64
healing_power::Int64
end
g = Gem("diamond", 13, 23000)
m = Medicine("cough syrup", 37, 222)
I tried to put the shared attributes into an extra struct, like in the following example. Advantage: No code repetition. Disadvantages: calling constructors and getting attributes (g.attributes.weight) is inconvenient.
abstract type GameObj end
struct GameObjAttr
name::String
weight::Int64
end
struct Gem <: GameObj
attributes::GameObjAttr
worth::Int64
end
struct Medicine <: GameObj
attritbutes::GameObjAttr
healing_power::Int64
end
g = Gem(GameObjAttr("diamond", 13), 23000)
m = Medicine(GameObjAttr("cough syrup", 37), 222)
The third example uses inner constructors, now the constructor calls are more easy to read and write, but now we have some code repetition in the inner constructors. Plus: Getting the shared attributes is still inconvenient:
abstract type GameObj end
struct GameObjAttr
name::String
weight::Int64
end
struct Gem <: GameObj
attributes::GameObjAttr
worth::Int64
Gem(name::String, weight::Int64, worth::Int64) = new(GameObjAttr(name, weight), worth)
end
struct Medicine <: GameObj
attributes::GameObjAttr
healing_power::Int64
Medicine(name::String, weight::Int64, healing_power::Int64) = new(GameObjAttr(name, weight), healing_power)
end
g = Gem("diamond", 13, 23000)
m = Medicine("cough syrup", 37, 222)
Is there another, better way to avoid this kind of code repetition? (Besides that: is it necessary to declare types inside the inner constructor, or can we leave that?)
Thanks in advance.
You can use Julia's metaprogramming abilities for this.
abstract type GameObj end
type_fields = Dict(
:Gem => (:worth, Int64),
:Medicine => (:healing_power, Int64)
)
for name in keys(type_fields)
@eval(
struct $name <: GameObj
name::String
weight::Int64
$(type_fields[name][1])::$(type_fields[name][2])
end
)
end
g = Gem("diamond", 13, 23000)
m = Medicine("cough syrup", 37, 222)
This is similiar to you copy-pasting the code but it allows you to do it programmatically. Note that we use $
to interpolate external values into the expression which is being executed in the loop.
Edit (based on question in comments):
If you want to be able to add an arbitrary number of fields for the different types you can make a minor modification to the above code:
abstract type GameObj end
type_fields = Dict(
:Gem => ((:worth, Int64),
(:something_else, Any)),
:Medicine => ((:healing_power, Int64),)
)
for name in keys(type_fields)
@eval(
struct $name <: GameObj
name::String
weight::Int64
$(map( x -> :($(x[1])::$(x[2])), type_fields[name])...)
end
)
end
g = Gem("diamond", 13, 23000, :hello)
m = Medicine("cough syrup", 37, 222)
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加