Not Even A Duck

Posted by scientific on September 28, 2006


Ruby is known as a duck-typed language: if an object walks and quacks like a duck, it’s a duck. It’s a behaviorialist approach, which is far different from how many strongly typed languages behave: using interfaces and class definitions in a very static manner. I decided to explore Ruby’s dynamic capacities.

Dave Thomas “Programming Ruby” book provided some insight into this area. In particular Tadayoshi Funaba’s code on p. 391 of the second edition gave the biggest leg up. I wanted to write similar code that was less obfuscated and easier to understand. By the time I finished, I came to understand Ruby in a much deeper way. I hope you might follow along and enjoy the ride..

Let’s try a few wacky Ruby snippets. You can save these snippets as complete ruby files and run them, standalone. Readers familiar with manually rewriting vtables during runtime and those who come from very dynamic languages such as Foxpro might not be as freaked out by all of this, where one can store code in a database and pull it out and execute. But I’m not sure what other common business languages let you redefine functions/methods in a class from code during runtime? Python? Any comments on this would be welcome.

#**File: kook.rb**
$kook_flag = TRUE

class Kook
  if $kook_flag
    def kookmethod
      puts "Kooky"
    end
  else
    def kookmethod1
      puts "Kooky1"
    end
  end
end

kooky = Kook.new
kooky.kookmethod
kooky.kookmethod1 # generates exception

Here we have dynamic, runtime conditions around method definitions that get interpreted as the class is first parsed/evaluated. Interestingly, this code doesn’t allow us to later change $kook_flag to FALSE and get access to the other method “kookmethod1″ when you create a new object or create a class that is subclassed off of Kook. “kookmethod1″ never gets defined at all: the code is there when the compiler parses the Class but once the compiler gets the structure from the code, it doesn’t re-evaluate the original code later.

Ok - so that’s kind of weird, or should I say kooky? But it gets way kookier. We can get around the limitation that Ruby doesn’t re-evaluate the original structure dynamically. It takes a little doing and a few sneaky tricks. Check out this code:

#** File: kook1.rb **
$original_kookmeth = <<-EOM
  def kookmeth
    puts "kookmeth"
  end
EOM

class Kook
  # define method ":kookmeth" using string $original_kookmeth as code
  def initialize
    if not Kook.method_defined?(:kookmeth)
      Kook.module_eval($original_kookmeth)
    end
  end
  # delete existing method passed in and create same method with dynamic new_method_code as code
  def overwrite_method(methodid, new_method_code)
    Kook.module_eval <<-EOM
      remove_method(:#{methodid.to_s})
      def #{methodid.to_s}
        #{new_method_code}
      end
    EOM
  end
end

kooky = Kook.new
kooky.kookmeth
kooky.overwrite_method(:kookmeth, "puts 'really kooky'")
kooky.kookmeth

To understand this you’ll need to know a little syntactic sugar.

<<-[word]

creates a string delimiter. Everything up until the next [word] is part of one string (this makes it easy to put new lines into strings). The syntax

"#{[var]}”

is used in a few places inside double quoted strings: [var] is evaluated and the result is dropped directly into the string at that location - more readable than the C way. This is crucial for making Module_eval really dynamic.

The Module class method “Module_eval” basically executes any string we pass into it as Ruby code in the context of whoever owns Module_eval (in this case the owner is class Kook). By throwing runtime variables into the string that Module_eval receives, we’re off to the races.. Overall, all we’re doing is creating a method during initialize based on a string. Then we pass in new code as a string, dropping the old method and creating a new method in its place using the new code for the method internals.

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

Comments