Learning Ruby Meta-programming with MetaKoans 1

Posted by ryan Sun, 23 Sep 2007 03:29:00 GMT

As I mentioned previously, the MetaKoans Ruby Quiz (#67) is a great way to learn meta-programming. However, it had some shortcomings. I've used MetaKoans as a training tool, and something I hear a lot is that it's unclear why certain things make a koan, or set of koans, pass. One reason for this confusion is that, often times while puzzling through the solution, a student will do something that causes multiple koans to pass at once. Due to the way that the koans are structured, I wasn't able to find a way to make a single koan pass at a time.

I've addressed this shortcoming by restructuring the koans so that the problem can be solved incrementally, one koan at a time. While restructuring the koans, I wrote a solution to each in turn, and saved that solution to its own knowledge file. Each file is a small refactoring from the one before it, ultimately building up to the final solution.

For the purposes of future training sessions, I've also started adding documentation to each refactoring, explaining how it changed from the previous one, and why it changed the way it did. The documentation isn't complete yet, but it's a start.

The restructured MetaKoans, along with the individual refactorings of my solution, can be found at http://github.com/ryankinderman/metakoans_training. Feel free to check it out.

A little explanation

You'll notice that the knowledge files in my solution follow the pattern: knowledge_for_koan_XX_Y.rb. The XX number is the koan that the knowledge is a solution for. The Y number is the ordered refactoring index, with 1 being the first, most straight-forward solution, and subsequent indices being refinements of the original.

The reason for this structuring is that, often times, the straight-forward, brute-force solution to a koan isn't always the optimal solution. So, I'd make refactorings to show how the code could, IMHO, be improved.

Finally

Thanks go to ara.t.howard for coming up with the original MetaKoans quiz. It's been an extremely informative tool for myself and many others.

Problems with the metakoans.rb Ruby Quiz 3

Posted by ryan Mon, 05 Feb 2007 23:25:00 GMT

There are indeed many ways to solve Ruby Quiz #67: metakoans.rb, as James Gray II says. By "solve," I mean getting all of the "koans" to pass. But you don't have to actually solve the quiz to make all of the koans pass. Here's a solution that passes all of the koans, but doesn't solve the problem completely:

class Module
  def attribute(params, &block)
    initial = nil
    if params.is_a?(Hash)
      name = params.keys[0]
      initial = params.values[0]
    else
      name = params
    end
    
    define_attribute_methods(name, initial, &block)
  end
  
  private
  
  def define_attribute_methods(name, initial, &block)
    define_method(name) do
      initial ||= instance_eval &block if block_given?

      @attr ||= initial
    end
    define_method(name + '=') do |value| 
      @attr = value
    end
    define_method(name + '?') do
      !@attr.nil?
    end
  end
end

While I was solving the solution, there were a number of times when all of the koans passed, but I had a sense that my solution wasn't correct. Similarly, when I was using this quiz as a Ruby teaching tool, a number of people told me that they solved the quiz, but don't know why the code passed all of the koans. For a self-testing quiz, this is a problem. Don't get me wrong, this quiz is awesome, but it'd be better if it had more thorough assertions to ensure that the "student" has correct "knowledge".

The first change that I made to the assertions was to change assertions like:

assert { (c.a = nil) == nil }
to
assert { c.a = nil; c.a == nil }
While these two assertions are similar, they are not the same, and the difference is subtle. The first assertion tests that the return value of the c.a= method is nil. The second assertion tests that the return value of the c.a method is nil. This is, I think, what the metakoans.rb author intended. Without using the second assertion, the return value of the c.a method could be incorrect after the internal attribute variable has changed via a call to c.a=.

The second change that I made was to make it so that, rather than using the number 44 as the default value for the 'a' attribute in every koan, I incremented the number by one. For koans that had a second attribute that took a block as a default value, I had them return a + 1 instead of simply a. This is important, because without this change, a single instance variable, such as the one I use in the erroneous solution above, could be used as the value for all attributes, and the koans would still pass.

You can get my metakoans.rb with the updated assertions here.

I'm not sure if there are other ways to make the koans pass without the solution being correct. If there are, please let me know, and I'll update the file.

Learn metaprogramming with Ruby Quiz #67: metakoans.rb 1

Posted by ryan Fri, 02 Feb 2007 21:21:00 GMT

If you're getting into metaprogramming with Ruby, a great way to learn is by solving the metakoans.rb Ruby quiz. I assigned it as a task for a training course recently, and one person told me that it was the most fun they've had programming.

I did, however, find a small problem with the way that the koans were structured. If you solve Koan 6 by setting a class-level variable for the default value, and then implement the attribute getter so that it returns that value if it's defined, koans 7-9 pass. To avoid this particular problem, I changed the use of the number 42 in koans 7-9 each to be a different number. This will ensure that the actions of one test do not fool the assertions of another.