December 29, 2006

Metaprogramming method closures in Ruby

night01.jpg

In my previous post, Of Closures, methods, procs, scope, and Ruby, I discussed how blocks and procs are closures in Ruby while methods are not.

To review, this won't work:

class MyClass
    @friend

    hello_string = "Hello"

    def initialize
        @friend = "Kitty"
    end

    def say_hello
        puts hello_string + " " + @friend  # Bzzt!  hello_string is not in scope
    end
end

my_object = MyClass.new

my_object.say_hello # Bzzt!

And neither will this (at the toplevel):

hello_string = "Hello, world!"

def say_hello
    puts hello_string  # Bzzt!  hello_string is not in scope
end

say_hello # Bzzt!

In response Kurt hypothesized we could get method closures in Ruby with a bit of metaprogramming.

And he's right.

As Bryce points out, Ruby's Module class has a private method nammed define_method that defines a new method given a symbol representing its name, and a block representing its body.

Because the second parameter is a block, and blocks can be closures, this gives us the key to defining method closures.

Within the context of a class definition:

class MyClass
    @friend
    hello_string = "Hello"

    def initialize
        @friend = "Kitty"
    end

    define_method(:say_hello) { puts hello_string + " " + @friend }
end

my_object = MyClass.new  

my_object.say_hello # Hello Kitty

Yay!

But suppose we want to define one-off methods from the top-level instead.

Because define_method is private, normally we wouldn't be able to call it outside a class definition, but we can get around this using the send hack.

Let's define a convenience method:

def lexdef(method_symbol, &block) 
    self.class.send :define_method, method_symbol, &block
end

And now we can define method closures from the toplevel:

hello_string = "Hello"

lexdef :say_hello do |friend|
    puts hello_string + " " + friend
end

say_hello "Kitty" # Hello Kitty

Voilà! An unobtrusive syntax for defining method closures!

I like it :)

As of Ruby 1.8, blocks don't yet support Ruby's first-class syntax for receiving block arguments. So this won't work:

hello_string = "Hello"

lexdef :say_hello do |friend, &formalize|   # Bzzt!  Syntax error
    puts hello_string + " " + yield(friend)
end

say_hello("Kitty") { |friend| "Ms. " + friend }

But word on the street is &block arguments to blocks will be supported in Ruby 1.9.

In Technology and Software

Posted at 06:34 PM | Permanent link

December 28, 2006

The Ruby send hack: how to gain access to an object's privates

lady_liberty01.jpg

I stumbled across an interesting Ruby hack that doesn't seem to be written down anywhere.

It turns out that private methods in Ruby aren't entirely private, and with a bit of indirection you can gain access to them as though they were public methods.

In Ruby 1.8 all we have to do is use the Object#send method.

To demonstrate:

class MyClass
    private
    def say_hello(name)
        puts "Let's go back to my place, #{name}."
    end
end

my_object = MyClass.new
So we get smacked if we try:
> my_object.say_hello
Nomethoderror: private method `say_hello' called for #<MyClass:0x820b4>
	from (irb):8
	from :0
but instead:
> my_object.send :say_hello, "world"
Let's go back to my place, world.

Touché!

Now, honestly, this strikes me as odd. Conceptually, I don't see why send should function differently from an ordinary method call with respect to access control.

The rubydoc for send says nothing of this, and there is some debate on the mailinglist as to whether Ruby 1.9's send will maintain this behavior.

But for now this can be very useful if you disagree with a class designer's method access choices.

In Technology and Software

Posted at 01:06 PM | Permanent link

December 26, 2006

Of closures, methods, procs, scope, and Ruby

giant-piano-06.jpg

One thing I hate about Ruby is that Ruby methods aren't closures.

For instance, this blows up:

hello_string = "Hello, world!\n"

def say_hello
    puts hello_string # bzzt! hello_string isn't in scope
end

say_hello # bzzt!

Which is a little odd because Ruby does support closures on procs:

hello_string = "Hello, world!\n"

say_hello = Proc.new do
    puts hello_string
end

say_hello.call # Hello, world!

It kinda sucks that every time I want to create a closure I'm forced to use the less succinct ".call" syntax.

So why can procs be closures while methods cannot?

After a bit of thinking I realized this comes down to a design tradeoff on whether or not we have to type "self" or "this" all over the place when writing methods as we do in Python and JavaScript.

In Ruby we don't have to do this, and Ruby accomplishes this by making procs and methods two different things with different scoping rules.

Proc bodies have a lexical scope like functions in Lisp and JavaScript, meaning that when Ruby encounters a free variable inside a proc body, its value is resolved within the context the proc was defined. This is what makes closures possible.

Methods are different, however. A method's context is the object to which they are bound. When Ruby encounters a free variable inside a method body, it assumes the variable refers to another method on the object, and this is what saves us from having to prefix same-object methods with "this" or "self".

To demonstrate:

irb(main):171:0> hello_string = "Hello, world!"
=> "Hello, world!"
irb(main):172:0> def say_hello
irb(main):173:1>     puts hello_string
irb(main):174:1> end
=> nil
irb(main):175:0> say_hello
NameError: undefined local variable or method `hello_string' for #
	from (irb):173:in `say_hello'
	from (irb):175
	from :0
irb(main):176:0> def hello_string
irb(main):177:1>     "Hello, world!"
irb(main):178:1> end
=> nil
irb(main):179:0> say_hello
Hello, world!
=> nil

Personally, I'm not sure I like this tradeoff.

Procs and methods are conceptually very similar. They are both composed of a list of arguments, a block of code, and a context in which the block is evaluated. I'm not sure they should be different things.

The way I see it, a method should just be a special case of proc that happens to have an implicit "self" parameter. In this case methods would inherit the proc scoping rules and therefore could be used as closures.

If this means we have to be verbose and type "self" to call other methods on the same object, I'm fine with that.

And in fact, this is how things work in JavaScript:

var helloString = "Hello";

var myObject = { 
    friend: "Kitty",
    sayHello: function() { 
        print(helloString + " " + this.friend);
    }
};

myObject.sayHello(); // Hello Kitty

Matz clearly recognizes that there are issues with Ruby's current scoping implementation, so maybe this is one of them.

But, hey, this kind of tradeoff may just be a Rubyish thing to do, and I probably dig closures more than most.

In Technology and Software

Posted at 05:54 PM | Permanent link | Comments (3)

December 20, 2006

Real life search

I love how whenever I lose something in my house, my first instinct is:

Oh, no problem. I'll just do a search.

And a short instant later...

Goddammit!

In Technology and Software

Posted at 08:39 PM | Permanent link

December 18, 2006

Five things you didn't know about me

starfish_dance01.jpg

There's a massive game of tag going around the blogosphere. I've been tagged by Bill and Dave.

So here goes. Five things you didn't know about me:

  1. I all but gave up Computer Science in favor of law my junior year of college. I even convinced my friend Evan to do the same... except he actually did :)
  2. I've been to London, Paris, Amsterdam, and Zurich, but only set foot west of the Mississippi in May of this year.
  3. I scored higher on the language portion of the SAT than the math portion.
  4. I play piano.
  5. I've been skydiving... twice.

I tag Ryan, Jeff, Kurt, Vinnie, and Anton.

In Matters that are otherwise worthwhile, Technology and Software

Posted at 10:30 PM | Permanent link

December 15, 2006

On Variable Naming in Code

night

Much is made of the pain of typing long variable names (or function names or class names), but code is usually read more often than it's typed. And when I'm reading code (even if it's my own), I'm grateful for names that err on the side of descriptiveness.

This means:

  • navigationElement instead of navigation or nav
  • queryString instead of query, queryStr, string, or str
  • libraryLookupUrl instead of libraryLookup or url

The exceptions are common language idioms which any programmer could reasonably be expected to know (ie: cons in Lisp, def in Python, x as an index into array loops). These should be abbreviated as their meaning is more intrinsic and independent of the name.

In Technology and Software

Posted at 08:37 AM | Permanent link

December 14, 2006

The Cleveland Surf Scene - For Serious

From the New York Times:

Yes, You Can Surf in Cleveland, Before the Brown Water Freezes

Surfers learn to avoid ice chunks the size of bowling balls. Some wear goggles to surf through freezing rain, which can sting their eyes like needles. That is a bad idea, Mr. Labbe said, because the goggles freeze to their faces.

...

To reach the lake, surfers drag their boards across snowdrifts and beaches littered with used condoms and syringes, Mr. Ditzenberger said. The most popular surf spot is Edgewater State Park. It is nicknamed Sewer Pipe because, after heavy rains, a nearby water treatment plant regularly discharges untreated waste into Lake Erie.

Ahh, to be in Cleveland again. This takes me back :)

The old gang at Edgewater Park:

edgewater2004

In Matters that are otherwise worthwhile

Posted at 02:12 AM | Permanent link

December 13, 2006

When designing a user interface ...

... imagine that your program is all that stands between the user and hot, sweaty, tangled-bedsheets-fingertips-digging-into-the-back sex.

Words to live by :)

I love xkcd.

In Technology and Software

Posted at 05:56 AM | Permanent link

December 12, 2006

Surprise! It's not Java.

Bubble Tea

So Java SE 6 is out.

I think the most exciting thing about this release is Sun's inclusion of Rhino, Mozilla's JVM-based Javascript engine, out of the box.

This could mean that Sun finally recognizes there *might* be life after Java for the JVM. If so, this is very good news.

In Technology and Software

Posted at 09:01 PM | Permanent link

December 11, 2006

Wall Street to Craigslist: Bitchsezwhat!!?

skate_01.jpg

Wall Street is finding it a little hard to swallow that Craigslist is more interested in maximizing user value than revenue.

To be fair, it's much easier for a privately held company like Cragislist to do this kind of thing than a publicly held one.

I'm sure Craig Newmark and friends have the leverage to be opinionated about how they run Craigslist, whereas when you're dealing with millions of different shareholders with no long-term commitment to the company, it's a lot harder to find a common goal other than "make money as fast as possible".

As tech becomes less capital intensive, it makes me wonder if we'll see privately held companies like Craigslist wield more and more power because they can try innovative business models, they can think long term, and they don't have to march to Wall Street's drumbeat.

In Technology and Software

Posted at 07:54 PM | Permanent link

December 06, 2006

Spam

alcohol.jpg

The New York Times reports that spam has doubled over the past year and spammers are combating Paul Graham's plan for spam by hiding their hello kitty vibrator ads in bitmap images where the filter can't pick them out.

I've been using Gmail as my mail client for a while now. If there were a way to whitelist who can send me file attachments (ie: anyone in my addressbook) my Gmail spam would be near-zero.

As it is 5-10 attachment spams get through the filter per day.

If solving spam means sacrificing email attachments, I'm all for it. Then again, maybe not (NSFW!!).

In Technology and Software

Posted at 10:40 PM | Permanent link

A PBF comics RSS feed WITH IMAGES

PBF195-The_Pacific_Council.jpg

Feed: /services/pbf-rss/

Source: pbf-rss.rb

Writing this script reminded me of just how much I love Ruby.

I was tempted to write in Common Lisp - just for fun, but there's no Lisp equivalent of Ruby's open-uri module. And dammit if open-uri isn't really convenient.

In Technology and Software

Posted at 01:02 AM | Permanent link