Julia 朱莉娅:非破坏性更新不可变类型变量

Julia 朱莉娅:非破坏性更新不可变类型变量,julia,immutability,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} 朱莉娅身上有类似的东西吗?这很棘手。在Clojure中,

假设有一种类型

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}

朱莉娅身上有类似的东西吗?

这很棘手。在Clojure中,这将适用于数据结构,即动态类型的不可变映射,因此我们只需调用适当的方法来添加/更改密钥。但是在处理类型时,我们必须进行一些反射,以便为该类型生成适当的新构造函数。此外,与Haskell或各种MLs不同,Julia不是静态类型的,因此我们不能简单地查看像
{foo with y=1}
这样的表达式,并计算出应该生成什么代码来实现它

事实上,我们可以构建一个Clojure式的解决方案;由于Julia提供了足够的反射和动态性,因此我们可以将该类型视为一种不可变的映射。我们可以使用
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\u slow
是有原因的。由于反射,它的速度远不及手写函数,比如
withy(foo::foo,y)=foo(foo.x,y,foo.z)
。如果
Foo
被参数化(例如
Foo{T}
with
y::T
),那么Julia将能够推断
with y(Foo,1.)
返回
Foo{Float64}
,但根本无法推断
with\u slow
。正如我们所知,这扼杀了螃蟹的表现

要使其与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
(nargs的
行本质上是一个手动展开的reduce,可实现此功能。)


最后,为了避免我被指责提供了一些有点疯狂的建议,我想警告大家,这在朱莉娅身上并不是那么地道。虽然我不能在不了解您的用例的情况下给出非常具体的建议,但通常最好让字段具有一组可管理的(小)字段和一组执行这些字段基本操作的函数;您可以在这些函数的基础上创建最终的公共API。如果您真正想要的是一个不可变的dict,那么最好使用专门的数据结构。

FixedSizeArrays.jl
包中还实现了
setindex
(最后没有
),这是一种有效的方法。

不幸的是,在Julia的例子中没有任何内置的语法。你必须自己创建一个函数。@colinfang我想当我用额外的关键字参数定义不可变类型时,它必须类似于一个“复制构造函数”…尝试将此方法应用到我的程序中时,没有发现,也许是Julia中不存在此语法的一些原因:我使用的是“智能构造函数”为了让我的数据类型预先计算相关字段+一些参数被传递给其他结构。所以,
fieldnames
不起作用。我已经在一个单独的类型中实现了我想要的,它构造了原始类型的实例。
@code_typed with(x, f"y" => 4., f"z" => "hello") # => ...::Foo{Int,Float64,String}