Sunday, March 17, 2013

Ruby Immutability

Recently, I was thinking about immutability in Ruby and how (if I was so inclined) to design classes and objects that contained immutable state.  Ruby is such a flexible language.  It's submissive; the language bends to your will with a minimum of effort, and to great enjoyment.  I wondered how to design an object that I could initialize with some state and have the object just hold onto that state and make it available to me, but not let go of it.  And not allow the values I pass into its initialization to be changed.

Any reasonably experienced Ruby developer knows that all Ruby objects have a method #freeze that lets an object's state be frozen (restricted from external change).

For example, if I have a String:

s = "Heynow"
s.upcase! # [Kablooee]

Freezing the s String causes its internal state to be fixed such that any calls to a method that would result in an internal state change raise an exception. That solves my problem, right?

For some class of immutability problems in Ruby, that actually does what you want. If you don't "own" the object you are freezing, though, calling #freeze on any object whose state you fancy fixing in place is like painting your favorite colors on your best friend's bicycle; it may feel right, but that don't make it right.

For the case where I wanted to preserve the state of objects handed to my classes but allow my own classes to manage objects as they pleased (IOW, borrow my neighbor's shovel and not damage it while clearing the snow off my driveway), I would need an idiom for fixing some but not all state in an object of my design.

I came up with a trick for tucking away objects handed to my initializer in a place where it would be difficult for me (or anyone else) to tamper with them. The case I am solving is one where an object of my design is handed some other object that I may initially modify or just hold onto, but want to fix its state at a specific point. In the example below, I have a class that receives an email value and I need to make sure that the email I'm handed doesn't get changed by whoever asks me for it.

In this approach, the value handed into the Profile object is tucked into a lambda and set as a method in the instance. This sets aside the email object passed into the initializer so that no other instance methods can modify the value of the email parameter (as they would be able to if email was simply referred to via an @email instance variable name).

The trouble with this approach, though, is that the email value itself is mutable by whoever passed it into the initializer of the Profile class. This is preventable (if true immutability is important).

By duplicating the parameter passed into the initializer and freezing the copy, the object can safely tuck the value away in a lambda and sleep soundly, knowing that no code (internally or externally) will tinker with a value that should not be changed.

This isn't perfect immutability, though. You could still write code to replace the method on this object instance with a new method that returns a different value for email. Any code doing this, though, isn't operating via an accidental course, changing state on objects unintentionally. The lambda, and dup/freeze combination is a reasonable tradeoff for creating a form of immutability in a language that is designed to be as mutable as you want it to be.

No comments: