29 Jan 2012 Stop Wasting Time Setting Up Frontend Projects

08 Jan 2012 How To Write Your Own DSL

Software by Josh

Blog of Josh Carver, programmer + designer


Pirating Ruby Methods For Fun And Profit

With all the SOPA coverage lately I wanted to post about software piracy (yarrrr!). But this post isn’t about the kind of software piracy you’re used to hearing about – it’s about pirating object methods using Ruby.

Sometimes while programming you might want to do what I call pirating a method – where you augment an object by borrowing a method from another module or class. Method pirating is fairly common to see in JavaScript code, especially to augment array-like objects like arguments which don’t have some useful methods that Arrays have, ex: Array.slice. Rubyists however aren’t quite the scallywags that JavaScript programmers are and don’t tend to do much pirating, but there’s times in Ruby where method pirating can be useful.

Including only what you need, nothing you don’t

Suppose that we have an extremely simple LinkedList implementation:

class LinkedList
  def add(value)
    return @head = Node.new(value) if @head.nil?

    current = @head
    while (!current.next.nil?)
      current = current.next
    end

    current.next = Node.new(value)
  end

  def each
    current = @head
    while (!current.nil?)
      yield current
      current = current.next
    end
  end

  def print
    each { |node| puts node.value }
  end

  private

  class Node
    attr_accessor :next
    attr_reader   :value

    def initialize(value)
      @value = value
    end

    # comparison operator
    def <=>(node)
      @value <=> node.value
    end
  end
end

Now imagine we’d like to add a map (collect) method onto our LinkedList. We could implement this ourselves, but the Enumerable module already has the exact method we want. There is no need to reinvent the wheel – so we should find a way to make use of it.

Option #1: Mixin Enumerable

We could easily add Enumerable’s map() method to our LinkedList implementation by simply including the Enumerable module into our class like so:

class LinkedList
  include Enumerable
  # rest of implementation
end

Since we already implemented an each() method, Enumerable’s map() methods will automatically work with our LinkedList class – nice! The downside to this approach is that a bunch of other methods we didn’t need came along for the ride like min(), max(), reduce() (inject) – in fact every instance method defined in Enunmerable is now a part of LinkedList.

Now in Enumerable’s case this isn’t a disaster – we only had to define one method (each) to make Enumerable work with LinkedList. But more complex mixins might require several helper methods to be defined to work with classes that include them. If we only want one or two methods from a module that don’t require extra helper methods – that could be a lot of extra work defining methods for functionality we don’t need.

Option #2: Pirating, Yarrr!

If you only want a few methods that a module provides, then a better option might be to pirate that method instead.

In my last post on writing DSL’s I showed how we could do this using instance_eval to change the execution context of a block. This seems like a reasonable approach to pirating methods as we could turn a regular module method into a block and then attempt to execute it in the context of the class stealing the method. Let’s give it a shot:

module Foo 
  def hi
    puts @words 
  end
end

class Boo
  def initialize
    @words = "boo"
  end 

  def hi
    method = Foo.instance_method(:hi)
    instance_eval &method
  end
end

boo = Boo.new
boo.hi

# wrong argument type UnboundMethod (expected Proc) (TypeError)

Uh-oh, looks like we wound up with an UnboundMethod, which isn’t associated with an object and thus uncallable. That’s ok though since in this case we’d like to bind our new unbound hi() method to the the Boo class so Boo.hi functions properly.

There is one issue though, UnboundMethods can only be bound to the same object type that they were created from. Ex. since we created an unbound method from Foo, we can only bind that method to another instance of Foo. Boo does not use Foo as a mixin so we’re currently unable to bind an UnboundMethod from Foo to an instance of Boo.

Fortunately Ruby allows to cheat. We can create a temporary Boo object that extends Foo without altering any other Boo instances using Object.extend. This then allows us to create an UnboundMethod that can be bound to a Boo instance even though it doesn’t mixin the Foo module!

Then all we have to do is bind our stolen method to the self variable in Boo and call that method.

class Boo
  def hi
    # temp class
    pirate = Boo.new.extend(Foo)

    # change execution context to this instance of Boo
    stolen_method = pirate.method(:hi).unbind().bind(self)

    # use our stolen method, prints "boo"
    stolen_method.bind(self).call
  end
end

We can easily apply the same technique to our LinkedList implementation to pirate the map() function off of Enumerable. Notice how we can pass parameters to call just like a normal method:

class LinkedList
  def map(&block)
    pirate = LinkedList.new.extend(Enumerable)
    stolen_method = pirate.method(:map).unbind.bind(self)
    stolen_method.call(&block)
  end
end

Generalizing

It would be quite tedious to have to pirate mulitple methods this way, but fortunately it’s relatively easy to abstract this technique into a module method that we can mixin and call inside our LinkedList class:

module MethodPirate
  def pirates(the_module, method)
    pirate_class = self.new.extend(the_module)
    stolen_method = pirate_class.method(method).unbind

    send :define_method, method do |*args, &block|
      stolen_method.bind(self).call(*args, &block)
    end
  end
end

class LinkedList
  include MethodPirate
  pirates Enumerable, :map
  pirates Enumerable, :inject
  # rest of implementation 
end

You do wan’t to be careful here since this implementation isn’t very performant, notice how we create a new object and closure everytime we call pirate(). So for production use you’ll most likely want to cache the the generated classes/unbound methods.

Wrapping Up

Hopefully you’ve learned something about how to pirate methods in Ruby, metaprogramming and how it pirating methods from modules might be useful in situations where you only want to mixin parts of a module (or group of modules). Questions, comments or ideas feel free to post.

blog comments powered by Disqus