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

Julia: how to avoid boilerplate code when structs sharing attributes

不同的结构(编辑:类型)应该共享一些属性的情况经常发生。如果我作为初学者做对了:在 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)

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

提前致谢。

你真的需要很多类型的结构来处理一个小的(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

谢谢!

Perhaps Julia's structs truly come into their own mainly when handling massed arrays of thousands of the same type.

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

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

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

您可以为此使用 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)