Julia struct 中的可变字段

mutable fields in Julia struct

提问人:stefabat 提问时间:1/23/2018 最后编辑:Seanny123stefabat 更新时间:3/1/2022 访问量:7546

问:

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

假设我想定义以下对象

struct Person
birthplace::String
age::Int
end

由于是不可变的,我很高兴没有人可以改变任何创造的东西,尽管如此,这也意味着当时间流逝时,我也无法改变它们......PersonbirthplacePersonage

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

mutable struct Person
birthplace::String
age::Int
end

我现在可以制作它们,但我没有以前的安全性,任何人都可以访问它并更改它。agebirthplace

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

struct Person
birthplace::String
age::Vector{Int}
end

其中显然是 1 元素。
我发现这个解决方案非常丑陋,而且绝对是次优的,因为我每次都必须使用方括号访问年龄。
ageVector

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

也许问题在于我错过了在 .如果是这样的话,你能给我解释一下吗?struct

结构 朱莉娅 不变性 可变

评论

0赞 Tasos Papastylianou 1/23/2018
从你表达问题的方式来看,我假设你不关心一个创建具有适当年龄的新对象的函数?例如:incrementageincrementage(p::Person) = Person(p.birthplace, p.age+1);
0赞 stefabat 1/23/2018
没错,我不想创建一个新对象。这个想法是一个对象,它从网络上获取一些信息并更新它的一些字段。由于它将每 10 秒左右轮询一次,因此每次创建一个新对象都不是我想要的。但无论如何都要感谢!
0赞 Tasos Papastylianou 1/23/2018
你可以用一个定制的内部构造函数来走一条路线,让你完全控制什么是可见的,什么是不可见的/可变的,就像这里一样: stackoverflow.com/q/39133424/4183191 (免责声明:无耻的插件自己的问题)
0赞 stefabat 1/23/2018
好!我实际上来自C++,因此我习惯了这种“类型”的对象。但这绝对不是语言的哲学,所以我不会走这条路。

答:

21赞 fredrikekre 1/23/2018 #1

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


我发现这个解决方案非常丑陋,而且绝对是次优的,因为我必须这样做 每次都用方括号访问年龄。

通常你会定义一个 getter,即你使用的类似的东西,而不是直接访问该字段。有了这个,您可以避免括号中的“丑陋”。age(p::Person) = p.age[1]

在这种情况下,我们只想存储单个值,也可以使用(或者可能是 0 维)的东西,如下所示:RefArray

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

评论

0赞 stefabat 1/23/2018
确实,这是一个玩具的例子......一般来说,我倾向于使用“get”函数,就像你说的,只用于最终用户可访问的字段,而在代码的深处(用户永远不必查看),我直接访问字段。另外,我不知道直接访问字段和通过函数访问字段之间是否存在性能差异。
1赞 fredrikekre 1/23/2018
您也可以将 getter 仅供内部使用,并且没有性能差异。
1赞 Chris Rackauckas 1/24/2018
没有性能差异,因为类型稳定的小函数只会内联函数,因此编译器基本上会摆脱函数调用并粘贴到字段访问中,使 getter 成为高级零成本抽象。
3赞 Chris Rackauckas 1/24/2018
与其使用 .至少在我上次检查时,存在性能差异。Array{Int,0}Ref
9赞 Colin T Bowers 1/24/2018 #2

您已经收到了一些有趣的答案,对于“玩具示例”案例,我喜欢存储出生日期的解决方案。但对于更一般的情况,我可以想到另一种可能有用的方法。定义为自己的可变结构和不可变结构。那是:AgePerson

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

请注意,我不能更改 的任一字段,但我可以通过直接访问可变结构中的字段来更改年龄。您可以为此定义一个访问器函数,即:PersonageAge

set_age!(x::Person, newage::Int) = (x.age.age = newage)

julia> set_age!(x, 12)
12

julia> x
Person("Sydney", Age(12))

另一个答案中讨论的解决方案没有错。它本质上是在完成同样的事情,因为数组元素是可变的。但我认为上述解决方案更整洁。Vector

评论

1赞 Tasos Papastylianou 1/24/2018
我本来打算提出一些类似的东西,但归根结底,它仍然相当难看。可以创建一个内部构造函数,它需要 an 而不是 ,这样至少你可以做 .此外,您可以使类型可调用,这样您至少可以这样做,而不是......但归根结底,它不会在 Int 表达式中无缝使用,除非人们也沿着转换路线前进(这可能不值得付出努力)。话又说回来,“1 向量”方法也不是。PersonIntAgePerson("Melbourne", 11)Agep.age()p.age.age
0赞 Colin T Bowers 1/24/2018
@TasosPapastylianou 是的,同意这一切看起来仍然有点混乱,但如果标准是可变和不可变对象的复合,我真的看不到更干净的方法。至少,正如你所说,大多数丑陋的东西都可以用一些直观的访问器功能来隐藏。如果你的对象有很多字段,你甚至可以进行元编程来避免代码膨胀。
0赞 stefabat 1/24/2018
我仍然更喜欢带有 或 的选项,因为我不必定义新结构。特别是,考虑到我创建只是为了让它成为其中的一个字段,而不是在其他任何地方使用它,这在我看来有点愚蠢。目前,由于通常在我正在创建的结构中,我有多个我希望可变的字段,因此我尝试以逻辑方式将它们组合在容器中(通常)。VectorRefstruct Agestruct PersonDict
4赞 Colin T Bowers 1/25/2018
@Batta 是的,这真的归结为个人喜好。我个人很乐意为一次性用例创建一个。有时,这种行为对于类型检查非常有用。例如,在金融中,您可以将价格和交易量都设置为类型,也可以是类型为 和 (只是包装器),但现在如果你的函数期望 和 ,你永远不会在代码中意外混淆两者。对于生产级代码来说,有用的技巧,像这样的事故会花费很多钱:-)structFloat64PriceVolumeFloat64PriceVolume
1赞 Colin T Bowers 4/16/2021
@PatrickT 哦,当然。我经常与股票数据打交道。我自己的代码库中存在的一些东西包括:、、等,或者、、、等等。例如,交易所在处理时间时变得非常有用,因为所有交易所都有不同的营业时间,所以我可以编写返回市场开盘和收盘时间的函数,并根据不同的交易所进行调度。abstract type Currency ; endstruct AUD <: Currency ; endabstract type Exchange ; endstruct NYSE <: Exchange ; endstruct ASX <: Exchange ; end
6赞 Marco Meer 3/1/2022 #3

在 Julia 1.8 中,您可以使用

mutable struct Person
       age::Int
       const birthplace::String
end

参见 https://docs.julialang.org/en/v1.8-dev/manual/types/#Composite-Types