次の「設計上の問題」に対するstackoverflowとJuliaのドキュメントの両方で答えが見つかりませんでした。
次のオブジェクトを定義したいとしましょう
struct Person
birthplace::String
age::Int
end
Person
は不変であるため、作成されたbirthplace
のPerson
を誰も変更できないことを嬉しく思います。それでも、時間が経つと、age
も変更できないことを意味します...
一方、タイプPerson
を次のように定義すると
mutable struct Person
birthplace::String
age::Int
end
これでage
にすることができますが、以前のbirthplace
の安全性がなく、誰でもアクセスして変更できます。
これまでに見つけた回避策は次のとおりです
struct Person
birthplace::String
age::Vector{Int}
end
ここで、明らかにage
は1要素のVector
です。
毎回角括弧で年齢にアクセスする必要があるため、このソリューションは非常に醜く、間違いなく最適ではないと思います。
オブジェクトに不変フィールドと可変フィールドの両方を含める、他のよりエレガントな方法はありますか?
おそらく問題は、struct
内ですべてを可変または不変にするという真の価値を見逃していることです。もしそうなら、それを説明してもらえますか?
この特定の例では、生年月日も不変であり、その情報から年齢を計算するのは簡単なので、年齢よりも生年月日を保存する方がよいようですが、これは単なるおもちゃの例です。
毎回角括弧で年齢にアクセスする必要があるため、このソリューションは非常に醜く、間違いなく最適ではないと思います。
通常、ゲッターを定義します。つまり、フィールドに直接アクセスする代わりに使用するage(p::Person) = p.age[1]
のようなものを定義します。これにより、角かっこによる「醜さ」を回避できます。
この場合、単一の値のみを格納する場合は、次のようなRef
(または場合によっては0次元のArray
)を使用することもできます。
struct Person
birthplace::String
age::Base.RefValue{Int}
end
Person(b::String, age::Int) = Person(b, Ref(age))
age(p::Person) = p.age[]
使用法:
Julia> p = Person("earth", 20)
Person("earth", 20)
Julia> age(p)
20
あなたはいくつかの興味深い答えを受け取りました、そして「おもちゃの例」の場合、私は生年月日を保存する解決策が好きです。しかし、より一般的なケースでは、役立つ可能性のある別のアプローチを考えることができます。 Age
をそれ自体の可変構造体として定義し、Person
を不変構造体として定義します。あれは:
Julia> mutable struct Age ; age::Int ; end
Julia> struct Person ; birthplace::String ; age::Age ; end
Julia> x = Person("Sydney", Age(10))
Person("Sydney", Age(10))
Julia> x.age.age = 11
11
Julia> x
Person("Sydney", Age(11))
Julia> x.birthplace = "Melbourne"
ERROR: type Person is immutable
Julia> x.age = Age(12)
ERROR: type Person is immutable
Person
のどちらのフィールドも変更できませんが、可変構造体age
のAge
フィールドに直接アクセスすることで年齢を変更できることに注意してください。このためのアクセサ関数を定義できます。
set_age!(x::Person, newage::Int) = (x.age.age = newage)
Julia> set_age!(x, 12)
12
Julia> x
Person("Sydney", Age(12))
別の回答で説明されているVector
ソリューションに問題はありません。配列要素は変更可能であるため、基本的に同じことを実行しています。しかし、私は上記の解決策はより良いと思います。