(Follow-up to my earlier question, Ruby: how can I copy a variable without pointing to the same object?)
I'm writing a simple Ruby program to make some substitutions in an .svg file. The first step is to pull information from the file and put it in an array. In order to keep from reading the file from disk every time this function is called, I'm trying to use the memoize design pattern - use a cached result on every call after the first one.
To do this, I'm using a global variable, defined just before the function. But even though I .dup
that variable to a local one before returning the local variable, the function that calls this one is still modifying the global variable.
Here is my actual code:
#memoize to keep from having to read original file on each pass
$svg_filedata_cache = [] #the global variable
def svg_filedata(filename)
if $svg_filedata_cache.empty?
File.open(filename, "r"){|f| $svg_filedata_cache = f.readlines}
end
svg_filedata_cache = $svg_filedata_cache.dup #try to copy it
return svg_filedata_cache #SHOULD point to a different object (but doesn't)
end
Two questions (answer either or both):
.dup
to copy it?Modifying the duped array will not affect the original. However modifications to the strings inside the array will be visible globally because the global array and the duped array still contain references to the same strings (dup does not perform a deep copy).
So either perform a deep copy (svg_filedata_cache = $svg_filedata_cache.map {|line| line.dup}
) or simply avoid mutating operations on the strings.
Enhancing the code a bit:
$svg_filedata_cache = [] #the global variable
def svg_filedata(filename)
# Use ||= for memoiziation
$svg_filedata_cache ||= File.open(filename, "r"){|f| $svg_filedata_cache = f.readlines}
$svg_filedata_cache.dup #shallow copying
end
Update: a simple trick to do deep copying in general is:
def deep_copy(obj)
Marshal.load(Marshal.dump(obj))
end
The global is probably not being modified, but the elements that it and your .dup reference are changing. To make it more canonical ruby, get rid of the global, use a class, and read the file in the initialize
function. (The constructor.) Make the array be an instance variable with @v.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With