Como fazer cópias profundas em Ruby

Muitas vezes é necessário fazer uma cópia de um valor em Ruby. Embora isso possa parecer simples, e seja para objetos simples, assim que você precisar fazer uma cópia de um dado estrutura com vários arrays ou hashes no mesmo objeto, você descobrirá rapidamente que existem muitos armadilhas.

Objetos e referências

Para entender o que está acontecendo, vamos ver um código simples. Primeiro, o operador de atribuição que usa um tipo POD (Plain Old Data) em Rubi.

a = 1
b = a
a + = 1
coloca b

Aqui, o operador de atribuição está fazendo uma cópia do valor de uma e atribuí-lo a b usando o operador de atribuição. Quaisquer alterações em uma não será refletido em b. Mas e algo mais complexo? Considere isto.

a = [1,2]
b = a
a << 3
coloca b.inspect

Antes de executar o programa acima, tente adivinhar qual será a saída e por quê. Não é o mesmo que no exemplo anterior, as alterações feitas no uma são refletidos em b, mas por que? Isso ocorre porque o Matriz O objeto não é do tipo POD. O operador de atribuição não faz uma cópia do valor, simplesmente copia o

instagram viewer
referência para o objeto Array. o uma e b variáveis ​​são agora referências para o mesmo objeto Array, quaisquer alterações em uma variável serão vistas na outra.

E agora você pode ver por que copiar objetos não triviais com referências a outros objetos pode ser complicado. Se você simplesmente fizer uma cópia do objeto, estará apenas copiando as referências aos objetos mais profundos, para que sua cópia seja chamada de "cópia superficial".

O que Ruby fornece: dup e clone

O Ruby fornece dois métodos para fazer cópias de objetos, incluindo um que pode ser feito para fazer cópias profundas. o Objeto # dup O método fará uma cópia superficial de um objeto. Para conseguir isso, o dup método chamará o initialize_copy método dessa classe. O que isso faz exatamente depende da classe. Em algumas classes, como Matriz, ele inicializará uma nova matriz com os mesmos membros que a matriz original. Isso, no entanto, não é uma cópia profunda. Considere o seguinte.

a = [1,2]
b = a.dup
a << 3
coloca b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
coloca b.inspect

O que aconteceu aqui? o Matriz # initialize_copy O método efetivamente fará uma cópia de uma matriz, mas essa cópia é em si uma cópia superficial. Se você tiver outros tipos não POD em sua matriz, use dup será apenas uma cópia parcialmente profunda. Será tão profundo quanto a primeira matriz, mais profundo matrizes, hashes ou outros objetos serão copiados apenas superficialmente.

Há outro método que vale a pena mencionar, clone. O método clone faz a mesma coisa que dup com uma distinção importante: espera-se que os objetos substituam esse método por um que possa fazer cópias profundas.

Então, na prática, o que isso significa? Isso significa que cada uma de suas classes pode definir um método clone que fará uma cópia profunda desse objeto. Isso também significa que você deve escrever um método clone para cada classe que fizer.

Um truque: Marshalling

"Marshallar" um objeto é outra maneira de dizer "serializar" um objeto. Em outras palavras, transforme esse objeto em um fluxo de caracteres que pode ser gravado em um arquivo que você pode "cancelar a retirada" ou "desserializar" posteriormente para obter o mesmo objeto. Isso pode ser explorado para obter uma cópia profunda de qualquer objeto.

a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
coloca b.inspect

O que aconteceu aqui? Marshal.dump cria um "despejo" da matriz aninhada armazenada em uma. Esse despejo é uma cadeia de caracteres binários destinada a ser armazenada em um arquivo. Abriga todo o conteúdo da matriz, uma cópia completa e profunda. Próximo, Marshal.load faz o oposto. Ele analisa essa matriz de caracteres binários e cria uma matriz completamente nova, com elementos da matriz completamente novos.

Mas isso é um truque. É ineficiente, não funciona em todos os objetos (o que acontece se você tentar clonar uma conexão de rede dessa maneira?) E provavelmente não é terrivelmente rápido. No entanto, é a maneira mais fácil de fazer cópias profundas que não sejam personalizadas. initialize_copy ou clone métodos. Além disso, o mesmo pode ser feito com métodos como to_yaml ou to_xml se você tiver bibliotecas carregadas para suportá-las.

instagram story viewer