Friday, January 8, 2010

Easy immutability in Ruby

One of the first paradigm shifts (did I really just use that term?) I ran into when going through ruby koans was the idea that function calls are actually just messages being passed between objects.  Though I had heard of this idea before I'd never actually dealt with it, and at first I wasn't sure what the big deal was.  The about proxy koan showed how to create a basic proxy class, but the utility of such a class wasn't immediately apparent.


Then I ran into a situation in my extreme solitaire project.  I needed to have my SolitaireBoard (not sure if I like that name) class be able to modify the various stacks of cards it used, so those stacks needed to be mutable.  However, when I returned those stacks to the player code I didn't want the player to be able to modify them, as it could let the player learn some things they shouldn't know (removing all the cards from a stack would cause the next hidden card to be shown, and the player shouldn't know what's there until they commit to a move).

I started thinking about how I would solve this in  Java or C++.  I could refactor my code so that I'd have IStackOfCards and IMutableStackOfCards interfaces, and have the board return IStackOfCards objects.  The player could still try manually casting to a mutable stack, which we don't want (no cheating!), so the next idea is to actually write a StackOfCardsProxy class that wraps the mutable stack but doesn't provide any mutating functions (or throws exceptions if they're called).  This would work, but it would involve a bunch of code that felt unecessary.

Ruby doesn't really use interfaces, so implementing a StackOfCardsProxy would involve a little less code (don't have to declare the interface), but still felt clunky.  Then I remembered two important things:
  1. All the methods on my classes that cause the object to mutate end in a "!"
  2. Methods are just messages, so I can write a generic Proxy easily.
I quickly threw together an ImmutableProxy class based on the Proxy class from koans, but made it reject any methods that end in a "!".  Here's what I have as of now:

# Proxies any object but doesn't allow any mutating method (methods ending in
# !) from being called.
class ImmutableProxy
  def initialize(target)
    @target = target
  end

  def method_missing(name, *args, &block)
    raise "Mutable methods not allowed" unless allowed?(name)
    @target.send(name, *args, &block)
  end

  def respond_to?(name, include_private=false)
    return false unless allowed?(name)
    @target.respond_to?(name, include_private)
  end

private
  def allowed?(name)
    ! name.to_s.end_with?('!') || !@target.respond_to?(name)
  end
end

The reason for the respond_to? check in allowed? is so that calling ! methods that don't actually exist behave the same.  We only want to throw the custom error if it's a method that does exist but isn't allowed by the proxy.  I'm still debating whether or not I want to keep it this way or just have it raise a NoMethodError for all ! methods (completely hide the fact that it could be mutable).

Right now this proxy doesn't handle attribute setters, but the classes that I'm wrapping with it don't use those right now.  I'd like to handle those as well, but I'm not quite sure how.




Proxy object in ruby that blocks mutating functions.

No comments:

Post a Comment