在Julia中生成自引用不可变类型

时间:2016-10-04 17:55:36

标签: julia

Julia lang文档解释了内部构造函数和new()函数如何用于构造自引用对象:

  type SelfReferential
      obj::SelfReferential
      SelfReferential() = (x = new(); x.obj = x)
    end

但是这种方法不适用于不可变类型,因为它实际上使用了未完全初始化的实例x的突变。

如何在Julia中生成自引用不可变对象?

1 个答案:

答案 0 :(得分:3)

由于您之前似乎使用过Haskell,我将从功能编程的角度来定制这个答案。自引用不可变类型的一个常见用例是创建一个惰性列表。

作为一种严格(即非懒惰)语言,不可变对象不可能直接引用自身。

然而,这并不排除使用像RefVector这样的可变对象间接引用自身。

对于惰性结构的特殊情况,我可能会建议将可变性限制在一个特殊对象,比如Lazy{T}。例如,

import Base: getindex
type Lazy
    thunk
    value
    Lazy(thunk) = new(thunk)
end

evaluate!(lazy::Lazy) = (lazy.value = lazy.thunk(); lazy.value)
getindex(lazy::Lazy) = isdefined(lazy, :value) ? lazy.value : evaluate!(lazy)

然后,例如,可以按如下方式创建一个简单的惰性列表:

import Base: first, tail, start, next, done, iteratorsize, HasLength, SizeUnknown
abstract List
immutable Cons <: List
    head
    tail::Lazy
end
immutable Nil <: List end

macro cons(x, y)
    quote
        Cons($(esc(x)), Lazy(() -> $(esc(y))))
    end
end

first(xs::Cons) = xs.head
tail(xs::Cons) = xs.tail[]
start(xs::Cons) = xs
next(::Cons, xs) = first(xs), tail(xs)
done(::List, ::Cons) = false
done(::List, ::Nil) = true
iteratorsize(::Nil) = HasLength()
iteratorsize(::Cons) = SizeUnknown()

确实像Haskell这样的语言一样有效:

julia> xs = @cons(1, ys)
Cons(1,Lazy(false,#3,#undef))

julia> ys = @cons(2, xs)
Cons(2,Lazy(false,#5,#undef))

julia> [take(xs, 5)...]
5-element Array{Int64,1}:
 1
 2
 1
 2
 1

此功能可能看起来很复杂,但幸运的是已经在Lazy.jl中实现了。

值得注意的是,由于类型不稳定和可变类型,上述代码会产生大量开销。如果您使用immutable的目标不是表达性,而是表现,那么显然这种方法是不合适的。但是通常不可能有一个引用自身的堆栈分配结构,所以在你想要最大性能的情况下,最好完全避免自引用。