2016-10-04 5 views
4

Юлии Ланг документация объясняет, как внутренняя Конструкторы и новая функция() может быть использована для построения самосправочные объектов:поколения автореферентных неизменных типов в Джулии

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

Однако этот подход не работает для неизменны потому что он по существу использует мутацию не полностью инициализированного экземпляра x.

Как я могу создать самореферентный неизменяемый объект в Джулии?

+4

Думаю, вы ответили на свой вопрос - вы не можете. –

+1

Кроме того, зачем вам это нужно? –

+0

@David, чтобы создать исполняемые, неизменные рекурсивные структуры данных. Единственный способ, которым я это знаю, - это абстрактные типы, которые должны принести штраф за производительность. Что я не понимаю, что в концепции неизменности типа должно запрещать самореферентное неизменное? – Bernd

ответ

3

Поскольку вы, кажется, раньше использовали Haskell, я буду адаптировать этот ответ с точки зрения функционального программирования. Общим примером использования самореферентных неизменяемых типов является создание ленивого списка.

Как строгий (то есть не ленивый) язык, невозможно, чтобы неизменный объект непосредственно ссылался на себя.

Это не исключает, однако, косвенное косвенное использование, используя изменяемый объект, такой как Ref или Vector.

Для конкретного случая ленивых структур я мог бы рекомендовать ограничить изменчивость специальным объектом, скажем, 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 не была выразительностью, а скорее производительностью, то, очевидно, такой подход не подходит. Но, как правило, невозможно создать структуру, распределенную по стеку, поэтому в тех случаях, когда требуется максимальная производительность, лучше всего полностью исключить самооценку.

+0

Благодарим вас за подробный ответ. Меня очень беспокоило производительность. Подход, который вы предлагаете, очень похож на PersistentList в FunctionalCollections.jl. Однако это подразумевает использование абстрактных типов и запуск @code_warntype с таким кодом, показывает мне много красных предупреждений, например Union {Cons, Nil}, поэтому я беспокоюсь о производительности. Но последнее предложение, которое самооценка невозможна в структуре, распределенной в стеке, объясняет это мне. – Bernd

+2

@ Списки Bernd на самом деле могут быть очень результативными (хотя и ленивыми списками менее).Способ определения списка исполнителей состоит в том, чтобы использовать 'typealias List {T} Nullable {Cons {T}}', что позволяет избежать нестабильности типа. –

+1

Если вы хотите, чтобы списки были как перформативными, так и самореферентными, просто используйте 'type' вместо' immutable'. –

Смежные вопросы