Come fare copie profonde in Ruby
Yuri Arcurs/Getty Images
Spesso è necessario fare una copia di un valore in rubino . Anche se può sembrare semplice, ed è per oggetti semplici, non appena devi fare una copia di una struttura dati con più array o hash sullo stesso oggetto, scoprirai rapidamente che ci sono molte insidie.
Oggetti e riferimenti
Per capire cosa sta succedendo, diamo un'occhiata a un semplice codice. Innanzitutto, l'operatore di assegnazione che utilizza un POD (Plain Old Data) digita Rubino .
a = 1
b = a
un += 1
mette b
Qui, l'operatore di assegnazione sta facendo una copia del valore di un e assegnandolo a b utilizzando l'operatore di assegnazione. Eventuali modifiche a un non si rifletterà b . Ma che dire di qualcosa di più complesso? Considera questo.
a = [1,2]
b = a
un<< 3
mette b.inspect
Prima di eseguire il programma sopra, prova a indovinare quale sarà l'output e perché. Questo non è lo stesso dell'esempio precedente, modifiche apportate a un si riflettono in b , ma perché? Questo perché il Vettore l'oggetto non è un tipo POD. L'operatore di assegnazione non esegue una copia del valore, ma copia semplicemente il riferimento all'oggetto Array. Il un e b le variabili sono ora Riferimenti allo stesso oggetto Array, tutte le modifiche in una delle variabili verranno visualizzate nell'altra.
E ora puoi capire perché copiare oggetti non banali con riferimenti ad altri oggetti può essere complicato. Se fai semplicemente una copia dell'oggetto, stai semplicemente copiando i riferimenti agli oggetti più profondi, quindi la tua copia viene definita 'copia superficiale'.
Cosa offre Ruby: duplicare e clonare
Ruby fornisce due metodi per creare copie di oggetti, incluso uno che può essere creato per eseguire copie profonde. Il Oggetto#dup il metodo creerà una copia superficiale di un oggetto. Per raggiungere questo obiettivo, il dup il metodo chiamerà il inizializza_copia metodo di quella classe. Ciò che fa esattamente dipende dalla classe. In alcune classi, come Array, inizializza una nuova matrice con gli stessi membri della matrice originale. Questa, tuttavia, non è una copia profonda. Considera quanto segue.
a = [1,2]
b = a.dup
un<< 3
mette b.inspect
a = [ [1,2] ]
b = a.dup
a[0]<< 3
mette b.inspect
Cosa è successo qui? Il Array#initialize_copy il metodo creerà effettivamente una copia di un array, ma quella copia è essa stessa una copia superficiale. Se nell'array sono presenti altri tipi non POD, utilizzare dup sarà solo una copia parziale. Sarà solo profondo come il primo array, più profondo matrici , hash o altri oggetti verranno copiati solo in modo superficiale.
C'è un altro metodo degno di nota, clone . Il metodo clone fa la stessa cosa di dup con una distinzione importante: ci si aspetta che gli oggetti sostituiscano questo metodo con uno in grado di eseguire copie profonde.
Quindi in pratica cosa significa? Significa che ciascuna delle tue classi può definire un metodo clone che farà una copia completa di quell'oggetto. Significa anche che devi scrivere un metodo clone per ogni classe che crei.
Un trucco: lo smistamento
'Marshalling' di un oggetto è un altro modo per dire 'serializzare' un oggetto. In altre parole, trasforma quell'oggetto in un flusso di caratteri che può essere scritto in un file che puoi 'unmarshalling' o 'unserialize' in seguito per ottenere lo stesso oggetto. Questo può essere sfruttato per ottenere una copia completa di qualsiasi oggetto.
a = [ [1,2] ]
b = Marshal.load( Marshal.dump(a) )
a[0]<< 3
mette b.inspect
Cosa è successo qui? Marshal.discarica crea un 'dump' dell'array nidificato archiviato in un . Questo dump è una stringa di caratteri binari destinata ad essere archiviata in un file. Ospita l'intero contenuto dell'array, una copia completa. Prossimo, Marshal.load fa il contrario. Analizza questo array di caratteri binari e crea un Array completamente nuovo, con elementi Array completamente nuovi.
Ma questo è un trucco. È inefficiente, non funzionerà su tutti gli oggetti (cosa succede se provi a clonare una connessione di rete in questo modo?) e probabilmente non è terribilmente veloce. Tuttavia, è il modo più semplice per creare copie profonde senza personalizzazioni inizializza_copia o clone metodi. Inoltre, la stessa cosa può essere fatta con metodi come a_yaml o a_xml se hai delle librerie caricate per supportarle.