Julia:非破坏性地更新不可变类型变量

时间:2016-07-30 08:10:28

标签: julia immutability

我们说有一种类型

immutable Foo
    x :: Int64
    y :: Float64
end

并且有一个变量foo = Foo(1,2.0)。我想使用bar构建一个新变量foo作为原型y = 3.0(或者,非破坏性更新foo生成一个新的Foo对象)。在ML语言(Haskell,OCaml,F#)和其他一些语言(例如Clojure)中,伪代码看起来像是一个成语

bar = {foo with y = 3.0}

朱莉娅有这样的事吗?

2 个答案:

答案 0 :(得分:4)

这很棘手。在Clojure中,这将使用数据结构,动态类型的不可变映射,因此我们只需调用适当的方法来添加/更改密钥。但是当使用 types 时,我们必须做一些反思来为该类型生成一个合适的新构造函数。此外,与Haskell或各种ML不同,Julia不是静态类型的,所以不要只看一个像{foo with y = 1}这样的表达式,并找出应该生成什么代码来实现它。

实际上,我们可以为此构建一个Clojure-esque解决方案;因为朱莉娅提供了足够的反思和动力,我们可以将类型视为一种不可变的地图。我们可以使用fieldnames来获取"键列表"按顺序(如[:x, :y]),我们可以使用getfield(foo, :x)动态获取字段值:

immutable Foo
  x
  y
  z
end

x = Foo(1,2,3)

with_slow(x, p) =
  typeof(x)(((f == p.first ? p.second : getfield(x, f)) for f in fieldnames(x))...)

with_slow(x, ps...) = reduce(with_slow, x, ps)

with_slow(x, :y => 4, :z => 6) == Foo(1,4,6)

然而,这被称为with_slow的原因。由于它的反射,它将无法接近,就像withy(foo::Foo, y) = Foo(foo.x, y, foo.z)这样的手写函数一样快。如果Foo被参数化(例如Foo{T}y::T),那么Julia将能够推断withy(foo, 1.)返回Foo{Float64},但不会能够推断出with_slow。我们知道,这会杀死 crab 的表现。

使其与ML和co一样快的唯一方法是生成有效等效于手写版本的代码。碰巧的是,我们也可以推出这个版本!

# Fields

type Field{K} end

Base.convert{K}(::Type{Symbol}, ::Field{K}) = K
Base.convert(::Type{Field}, s::Symbol) = Field{s}()

macro f_str(s)
  :(Field{$(Expr(:quote, symbol(s)))}())
end

typealias FieldPair{F<:Field, T} Pair{F, T}

# Immutable `with`

for nargs = 1:5
  args = [symbol("p$i") for i = 1:nargs]
  @eval with(x, $([:($p::FieldPair) for p = args]...), p::FieldPair) =
      with(with(x, $(args...)), p)
end

@generated function with{F, T}(x, p::Pair{Field{F}, T})
  :($(x.name.primary)($([name == F ? :(p.second) : :(x.$name)
                         for name in fieldnames(x)]...)))
end

第一部分是生成类似符号的对象f"foo"的hack,其值在类型系统中是已知的。生成的函数就像一个宏,它接受类型而不是表达式;因为它可以访问Foo和字段名称,它可以生成本代码的手动优化版本。如果参数化Foo,您还可以检查Julia是否能够正确推断输出类型:

@code_typed with(x, f"y" => 4., f"z" => "hello") # => ...::Foo{Int,Float64,String}

for nargs行本质上是一个手动展开的reduce,可以启用此功能。)

最后,为了避免被指责给出一些有点疯狂的建议,我想提醒一下,这并不是朱莉娅那种惯用语。虽然我不能在不知道您的用例的情况下提供非常具体的建议,但通常最好的是具有可管理(小)字段集和一小组函数的字段,这些字段对这些字段进行基本操作;您可以在这些函数的基础上构建最终的公共API。如果你想要的东西真的是一个不可改变的字典,你只需要使用专门的数据结构就可以了。

答案 1 :(得分:0)

setindex包中还实现了!(最后没有FixedSizeArrays.jl),这样做有效。