Struct Julia结构中的可变域

Struct Julia结构中的可变域,struct,julia,immutability,mutable,Struct,Julia,Immutability,Mutable,我无法在stackoverflow和Julia文档中找到以下“设计问题”的答案: 假设我想定义以下对象 struct Person birthplace::String age::Int end 由于人是不可变的,我很高兴没有人能改变任何人的出生地,尽管如此,这也意味着随着时间的推移,我也不能改变他们的年龄 另一方面,如果我将类型Person定义为 mutable struct Person birthplace::String age::Int end 我现在可以让他们变老了,但我没有以前

我无法在stackoverflow和Julia文档中找到以下“设计问题”的答案:

假设我想定义以下对象

struct Person
birthplace::String
age::Int
end
由于
是不可变的,我很高兴没有人能改变任何
的出生地,尽管如此,这也意味着随着时间的推移,我也不能改变他们的
年龄

另一方面,如果我将类型
Person
定义为

mutable struct Person
birthplace::String
age::Int
end
我现在可以让他们
变老了
,但我没有以前在
出生地
上的安全装置,任何人都可以访问并更改它

到目前为止,我发现的解决方法如下

struct Person
birthplace::String
age::Vector{Int}
end
其中显然
age
是一个单元素
向量

我发现这个解决方案非常难看,而且肯定是次优的,因为我每次都要用方括号来表示年龄

有没有其他更优雅的方法在对象中同时包含不可变字段和可变字段


也许问题在于我忽略了在
结构
中任何东西都是可变或不可变的真正价值。如果是这样的话,你能给我解释一下吗?

对于这个特定的例子来说,存储出生日期似乎比存储年龄更好,因为出生日期也是不可变的,根据这些信息计算年龄也很简单,但也许这只是一个玩具例子


我发现这个解决方案非常难看,而且肯定是次优的 每次使用方括号访问年龄

通常,您会定义一个getter,例如
age(p::Person)=p.age[1]
,您使用它来代替直接访问字段。这样可以避免括号中的“丑陋”

在这种情况下,如果我们只想存储单个值,也可以使用
Ref
(或者可能是0维
数组
),类似于:

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))

另一个答案中讨论的
向量
解决方案没有问题。因为数组元素是可变的,所以它基本上完成了相同的事情。但是我认为上面的解决方案更简洁。

从你表达这个问题的方式来看,我假设你不喜欢一个
incrementage
函数来创建一个具有正确年龄的新对象?e、 g.
incrementage(p::Person)=Person(p.出生地,p.年龄+1)没错,我不想创建新对象。idea是一个对象,它从web上获取一些信息并更新一些字段。。因为它将每隔10秒左右进行一次轮询,所以每次创建一个新对象并不是我想要的。。不过还是要谢谢你!您可以使用定制的内部构造函数来完全控制什么是可见的,什么是不可见的/可变的,如下所示:(免责声明:无耻地插入自己的问题)很好!我实际上是来自C++,所以我习惯了“类型”的对象。但这绝对不是语言的哲学,因此我不会走这条路。事实上,这是一个玩具的例子。。。一般来说,我倾向于使用“get”函数,就像您所说的,只针对最终用户可以访问的字段,而在代码的深处(用户永远不必查看),我直接访问字段。另外,我不知道直接访问字段和通过函数访问字段之间是否存在性能差异。您可以使用getter仅供内部使用,并且没有性能差异。没有性能差异,因为类型稳定的小函数只会内联函数,因此,编译器将基本上摆脱函数调用并粘贴到字段访问中,使getter成为一个高级零成本抽象。与其使用
Array{Int,0}
,不如使用
Ref
。至少我上次检查时发现性能有差异。我本来打算提出同样的建议,但最后还是很难看。你可以为
Person
创建一个内部构造函数,它采用
Int
而不是
Age
,这样至少你可以做
Person(“墨尔本”,11)
。您还可以使
Age
类型可调用,这样您至少可以执行
p.Age()
而不是
p.Age.Age
。。。但归根结底,它不能无缝地用于Int表达式,除非我们也进行了转换(这可能不值得付出努力)。再说一次,“1的向量”方法也是如此。@TasosPapastylianou是的,同意这一切看起来还是有点混乱,但如果标准是可变和不可变对象的组合,我真的看不到更干净的方法。至少,正如你所说,大多数丑陋可以通过一些直观的访问器功能隐藏起来。如果你的对象有很多字段,你甚至可以通过元编程来避免代码膨胀。我仍然喜欢使用
Vector
Ref
选项,因为我不必定义新的结构。特别是,考虑到我创建
struct Age
只是为了让它成为
struct Person
中的一个字段,而不在其他任何地方使用它,我觉得这有点愚蠢。目前,由于在我创建的结构中,通常有多个字段是可变的,所以我尝试将它们组合到容器中(通常是
Dict