tags:

views:

371

answers:

4

Is there an easy way to test whether an object is a immutable (numbers, nil) or not (Array, Hash, objects)? In other words, could it be changed by side effects from other code?

Motivation: I want to create a versioned value store, but some of the data is arrays. Some of the arrays will store custom objects, and I could invert the relationship by storing the 'in' property and searching for it. But I'd also like to be able to store arrays of symbols, other arrays, etc.

+3  A: 

I found an inefficient way:

class Object
  def primitive?
    begin
      self.dup
      false
    rescue TypeError
      true
    end
  end
end
Justin Love
+4  A: 

There are no primitive objects in Ruby. This can therefore not be detected in a straightforward manner.

Can't you simply use Marshal or YAML for your versioned store? Then you'll get loading and saving of all object types for free. Why reinvent the wheel?

I don't know what you want to achieve exactly, but looking at the source of YAML may be interesting to see how they handle this problem. The Ruby YAML encoding implementation simply implements the to_yaml method for all relevant classes. See yaml/rubytypes.rb.

wvanbergen
It's in-memory and live (holds the application state as it's running) so those seemed kind of heavy weight. Think more like Prolog than like Mercurial. I'll have a look at the YAML source for ideas.
Justin Love
A: 

Another difference: natively immutable objects can't be frozen, but they still return false from frozen?

5.freeze.frozen? == false

Freeze doesn't raise an exception (unlike dup) However it does (permanently!) modify mutable objects.

I've found that I can (at least in it's current state) arrange my app to work with frozen objects, and ruby will give me an exception if I try to modify them directly. However freeze only affects the first level of the object, and arrays, etc. stored in it can still be modified.

This only applies to 1.8 - 5.frozen? returns true in ruby1.9 (but not in irb1.9)

Justin Love
+2  A: 

The idea of mutability doesn't really apply in Ruby the same way as in other languages. The only immutable object is a frozen one. You can even add methods and instance variables to Fixnums. For example:

class Fixnum
  attr_accessor :name
end
1.name = "one"
2.name = "two"

Obviously, the vast majority of the time, people aren't going to be pathological enough to add attributes to Fixnum, but the point is, no unfrozen object is truly immutable.

If you can come up with a cannonical list of classes that you want to assume are immutable, you could just go through and give them all an immutable?() method that returns true (and Object a version that returns false). But like wvanbergen said, the best way to make sure your copy of an object doesn't change is to deep-copy it with Marshal.

Chuck