tags:

views:

136

answers:

3

(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):

  1. Why do other functions, which take in and modify the value returned here, also affect the global variable, even though I used .dup to copy it?
  2. I'm new to Ruby and I'm sure this isn't the most Rubyesque way to do this (and I don't like global variables, anyway). Can you suggest a better strategy?
+4  A: 

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.

sepp2k
I didn't realize that every string in the array was its own object! I guess it really is true that "EVERYTHING in Ruby is an object." :)
Nathan Long
+2  A: 

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
khelll
So ||= means "if the left side is false (empty), use the right side?"
Nathan Long
it means assign the right side value to the left side variable only if that variable is't already set.
khelll
+1  A: 

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.

DigitalRoss