2015-06-17 4 views
7

Итак, вот настройка. У меня есть несколько составных типов, определенных с их собственными полями и конструкторами. Позволяет показать две упрощенные компоненты здесь:Как получить глубокие копии композитных типов Julia?

type component1 
    x 
    y 
end 

type component2 
    x 
    y 
    z 
end 

Теперь я хочу, чтобы определить новый тип таким образом, что он может сохранить массив размера K ранее определенных составных типов в нем. Таким образом, это параметрический составной тип с двумя полями: один является целым числом K, а другой представляет собой массив размера K переданного типа.

type mixture{T} 
    components::Array{T, 1} 
    K::Int64 

    function mixture(qq::T, K::Int64) 
     components = Array{typeof(qq), K} 
     for k in 1:K 
      components[k] = qq 
     end 
     new(components, K) 
    end 
end 

Но это неправильный способ сделать это. Поскольку все K-компоненты относятся к одному объекту и манипулирование mix.components [k] будет влиять на все K-компоненты. В python я могу исправить это с помощью deepcopy. Но глубокая копия в Джулии не определена для составных типов. Как решить эту проблему?

ответ

11

Ответ на Ваш вопрос:

При определении нового типа в Джулии, он является общим, чтобы расширить некоторые из стандартных методов в Base к новому типу, в том числе deepcopy. Например:

type MyType 
    x::Vector 
    y::Vector 
end 
import Base.deepcopy 
Base.deepcopy(m::MyType) = MyType(deepcopy(m.x), deepcopy(m.y)) 

Теперь вы можете вызвать deepcopy над экземпляром MyType, и вы получите новый, по-настоящему независимой, копию MyType как выход.

Примечание: мой import Base.deepcopy на самом деле избыточен, так как я указал Base в определении моей функции, например. Base.deepcopy(m::MyType). Тем не менее, я сделал оба из них, чтобы показать вам два способа расширения метода от Base.

Второе примечание, если тип имеет много полей, вы можете вместо перебирать поля, используя deepcopy следующим образом:

Base.deepcopy(m::MyType) = MyType([ deepcopy(getfield(m, k)) for k = 1:length(names(m)) ]...) 

Комментарий на код:

Во-первых, стандартная практика в Julia для использования имени типа, например Component1 вместо component1. Конечно, вам не обязательно это делать, но ...

Во-вторых, из Julia docs performance tips: Объявлять конкретные типы для полей композитных типов. Обратите внимание, что вы можете параметризовать эти объявления, например.

type Component1{T1, T2} 
    x::T1 
    y::T2 
end 

В-третьих, вот как я бы определил свой новый тип:

type Mixture{T} 
    components::Vector{T} 
    Mixture{T}(c::Vector{T}) = new(c) 
end 
Mixture{T}(c::Vector{T}) = Mixture{eltype(c)}(c) 
Mixture(x, K::Int) = Mixture([ deepcopy(x) for k = 1:K ]) 

Есть несколько важных различий здесь между моим кодом и вашим. Я пройду через них по одному.

Ваше поле K было излишним (я думаю), потому что оно просто должно быть длиной components.Так что может быть проще просто распространить метод length к новому типу следующим образом:

Base.length(m::Mixture) = length(m.components) 

и теперь вы можете использовать length(m), где m является экземпляром Mixture, чтобы получить то, что было ранее сохраненными в K поле.

Внутренний конструктор в вашем виде mixture был необычным. Стандартная практика заключается в том, что внутренний конструктор принимает аргументы, которые соответствуют друг другу (последовательно) полям вашего типа, а затем остальная часть внутреннего конструктора просто выполняет любые проверки ошибок, которые вы хотели бы сделать, всякий раз, тип. Вы отклонились от этого, так как qq не был (обязательно) массивом. Такое поведение лучше зарезервировано для внешних конструкторов. Итак, что я сделал с конструкторами?

Внутренний конструктор Mixture на самом деле ничего не делает, кроме как передать аргумент в поле через new. Это связано с тем, что в настоящее время нет никаких проверок ошибок, которые мне нужно выполнить (но я могу легко добавить их в будущем).

Если я хочу назвать этот внутренний конструктор, мне нужно написать что-то вроде m = Mixture{MyType}(x), где x - Vector{MyType}. Это немного раздражает. Поэтому мой первый внешний конструктор автоматически передает содержимое {...} с использованием eltype(x). Из-за моего первого внешнего конструктора я могу теперь инициализировать Mixture, используя m = Mixture(x) вместо m = Mixture{MyType}(x)

Мой второй внешний конструктор соответствует вашему внутреннему конструктору. Мне кажется, что вы после этого должны инициализировать Mixture с тем же компонентом в каждом поле components, повторяющийся K раз. Поэтому я делаю это с пониманием петли над x, которое будет работать до тех пор, пока метод deepcopy определен для x. Если метод deepcopy не существует, вы получите сообщение об ошибке No Method Exists. Этот вид программирования называется утиным типом, а в Julia обычно не существует ограничения производительности при его использовании.

Обратите внимание, что мой второй внешний конструктор вызовет мой первый внешний конструктор K раз, и каждый раз мой первый внешний конструктор вызовет мой внутренний конструктор. Функциональность вложенности таким образом будет в более сложных сценариях массовым сокращением дублирования кода.

Извините, этого много, что я знаю. Надеюсь, поможет.

+0

Благодарим вас за полный ответ. Я был знаком с концепцией множественной диспетчеризации, но я не был уверен, что лучший способ - изменить Base.deepcopy. Также спасибо за комментарии. В отношении третьего комментария и устранения необходимости указывать тип в конструкторе, хотя мне удалось использовать ваш метод, но я не совсем понимаю его! – Adham

+0

@Adham Я обновил свой ответ. Если все еще неясно, дайте мне знать, и я попробую еще раз :-) –

+0

Спасибо. Теперь это намного яснее (для меня как новичок в Джулии: D) – Adham

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