Julia:结构共享属性时如何避免样板代码



不同的结构(编辑:类型(应该共享一些属性的情况经常发生。如果我作为初学者做对了:在 Julia 中,您可以扩展减法类型,但它们可能没有任何属性。具体类型(=结构(不可扩展。那么,有没有办法避免代码重复(对于属性名称和权重(,就像在给定的例子中一样?

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)

我尝试将共享属性放入一个额外的结构中,如以下示例所示。优点:没有代码重复。缺点:调用构造函数和获取属性(g.attributes.weight(不方便。

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)

第三个示例使用内部构造函数,现在构造函数调用更易于读写,但现在我们在内部构造函数中有一些代码重复。另外:获取共享属性仍然很不方便:

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)

有没有另一种更好的方法来避免这种代码重复? (除此之外:是否有必要在内部构造函数中声明类型,或者我们可以保留它吗?

提前谢谢。

你可以使用Julia的元编程能力来实现这一点。

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)

这类似于复制粘贴代码,但它允许您以编程方式执行此操作。请注意,我们使用$将外部值插入到循环中执行的表达式中。

编辑(基于评论中的问题(:

如果您希望能够为不同类型的字段添加任意数量的字段,您可以对上面的代码进行细微的修改:

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)

对于一个小型(n <1000(广泛变化的东西列表,你真的需要很多类型的结构吗? 也许 Julia 的结构真正发挥作用主要是在处理大量数组时 数千种相同类型。你是在计划那种平行的大规模,还是只是一个 异构列表?

但是,有一个内置类型是为这样的用例制作的,即 Dict。

GameObject = Dict{String, Any}
g = GameObject("name" => "diamond", "worth" => 23000)
m = GameObject("name" => "medicine", "healing_power" => 222, "worth" => 37)
coin = GameObject("worth" => 1)

这可以很好地工作。随之而来的小烦恼是需要引号 括号中的标签,但可以使用访问器函数修复:

# constructor...
newmedicine(worth, healingpower) = GameObject("name" => "medicine", 
"worth" => worth, "healing_power" => healingpower)
name(g::GameObject) = try g["name"]; catch; "" end
for o in [g, m, coin]
println(name(o))
end

谢谢!

也许 Julia 的结构真正发挥作用主要是在处理数千个相同类型的大量数组时。

好的,如果我没猜错的话,在面向对象语言中常见的类型层次结构不符合 Julias 的高性能功能。这是有道理的。

我稍微改变了你的代码示例,现在键是 Sympbols。

GameObject = Dict{Symbol, Any}
makegem(weight, worth) = GameObject(:name => "gem", :weight => weight, :worth => worth)
makemedicine(weight, healing_power) = GameObject(:name => "medicine", :weight => weight, :healing_power => healing_power)
addweight(o1::GameObject, o2::GameObject) = o1[:weight] + o2[:weight]
g = makegem(13, 23000)
m = makemedicine(37, 222)
addweight(g,m) # = 50

最新更新