Scoped mixins

A while ago I read one of Peter Harkins’s posts about open classes in Ruby, and how insanely awesome and useful they are. As you can probably tell, I wholeheartedly agree. Open classes are just one of those things that make me choose Ruby over pretty much anything else, whenever I get the chance to do so (though if I’d have to name the one killer feature, I’d probably say ‘blocks’).

I vastly prefer this:

class Array
  def rand
    self[Kernel::rand(self.size)]
  end
end
 
a = [1, 1, 2, 42, 24024]
a.rand # => 24024
a.rand # => 42

to this:

def array_rand(arr)
  arr[Kernel::rand(arr.size)]
end
 
a = [1, 1, 2, 42, 24024]
array_rand(a) # => 1
array_rand(a) # => 24024

Yuck. Looks like PHP1!

Open classes are also very dangerous. But hey, that’s Ruby for ya. Ruby trusts programmers to do the right thing, rather than patronise them. If they don’t, then that’s their problem, not Ruby’s. I think that makes a whole lot of sense. In a way it’s also kind of brave, because Ruby gets a lot of flak for this from people who think it’s irresponsible to give programmers this kind of power. Good for them, I guess – I think it’s rad.

I won’t go into much detail here about why they could be considered harmful; enough has already been written about that. What it usually boils down to is this: you might be modifying a method in an existing class somewhere. Someone else might be doing this too, while using your code. If he isn’t aware of what you did, he will expect the method he is modifying to exhibit its usual behaviour. Oops. Debugging this kind of thing is pretty annoying, as you can well imagine.

One could argue that that someone else should have taken the time to get to know your code better before using it. But I think reopening classes (monkey patching, if you will) is so entrenched in Ruby’s culture that this is not realistic. Rails does it, so it’s probably okay, right?

Well, in some cases it isn’t; particularly when other people are going to use your code, e.g. when you’re writing a library. I use this feature a lot; it keeps my code clean, readable and object oriented. I need Symbol#to_proc and Array#rand! But I don’t do this when I’m writing code that other people are just going to use, without taking the time to study its internal workings. Expecting them to do that is unreasonable in most cases. Thank god I don’t write a lot of library code, because it’s something I really miss.

Wouldn’t it be cool if… we could monkey patch on a per scope basis? We could edit the interfaces of objects we use to our liking, and nobody would ever need to know! I hope I didn’t get your hopes up with the title of this post, because it can’t be done. Yet. Well, probably never. I don’t know :(

Of course, there is mixology, or the more frivolously named mixico, one of _why’s creations. These two basically do the same thing; they are C extensions that allow you to mix out a module from a class (e.g. remove it from the inheritance chain). By default, Ruby only allows you to mix them in.

Alas, they are of little use for what I want to achieve, as far as I can tell. What it comes down to is that I want to change the interface of an object within a static scope; say, within a module declaration, or maybe within a single source file. You could do something like:

require "rubygems"
require "mixology"
 
module Translator
  module StringExtensions
    def pig_latin
      self.split(" ").collect do |word|
        word[1..-1] + word[0,1] + "ay"
      end.join(" ")
    end
  end
 
  str = "Hello world"
 
  str.mixin StringExtensions
  puts str.pig_latin # => "elloHay orldway"
 
  str.unmix StringExtensions
  puts str.pig_latin # NoMethodError: undefined method `pig_latin' for "Hello world":String
end

No comments about my pig_latin implementation please, I know it stinks. Just an example.

Anyway, this is worthless. For starters, it looks like crap. It would help if we could do this on a per class basis, instead of per object, but even then it would still be pretty verbose. In fairness, I don’t really have any ideas how to solve this problem syntactically in an elegant way. But that doesn’t matter because this doesn’t do what I want: if my object were to leave the static scope of the Translator module (like when I’d pass it as a parameter to an external method), it would still include the mixin. Exactly what I’m trying to avoid.

I guess I could try and write something like this myself but it really sounds like an arduous task, if it is possible at all. For starters, I don’t know anything about Ruby’s internal workings, or writing native extensions. My knowledge of C is fairly decent, but I’m not very fond of the language. In addition, I’d have to figure out a good solution for syntax. Nope… i’m affraid scoped mixins (or maybe mixin closures?) will remain but a dream.

In Ruby at least. Only a few days after I started thinking about this idea, I came across _why’s newest project on GitHub. It’s a mixin-oriented language called ‘Potion’. Conceptually, it separates behaviour from data, and allows local modifications to the behaviour part of objects: scoped mixins! I haven’t played with it, but it looks like an interesting experiment, to say the least.

Update: well what do you know. This seems to do what I described, pretty much. But it looks a bit scary.

Notes

  1. ↑1 Okay, so maybe I did that on purpose. Sue me :D

4 Comments

  1. Posted January 22, 2009 at 10:58 pm | Permalink

    it’s not THAT scary, give it a try :)

    http://github.com/coderrr/monkey_shield

  2. Sander
    Posted January 23, 2009 at 12:02 am | Permalink

    I will, actually! The whole context switching thing is a bit of a different approach than I’d expected, but I’m definitely going to check it out :)

  3. anon
    Posted March 2, 2010 at 5:31 pm | Permalink

    I don’t see anything that looks like PHP that doesn’t look like any other language. Try harder next time :-/

  4. Sander
    Posted March 2, 2010 at 5:37 pm | Permalink

    I was talking about the PHP naming convention, which I purposefully mimiced. An inside joke with a friend, I thought it would be obvious. It definitely wasn’t intended to be taken seriously.

Post a Comment

A subset of HTML is supported. Please use <code> and <pre> for code samples. You can enable syntax highlighting like this: <pre lang="ruby">. This also works for Javascript, Python, and probably most other languages you can think of.

Your email is never published nor shared. Required fields are marked *

*
*