Apr 26, 2012

Ruby for the C# developer–Open Classes & method_missing

In the last post we covered the basics of the Ruby language from a C# developers perspective. We left off with a short discussion about one major fundamental difference between Ruby and C#: Mutable Types

In this post I wanted to cover the subject in a bit more depth and explain what Open classes are and what the method_missing method is used for.

Open classes means you can change the definition of any class at any time. Usually this would be done to add behavior to a class. Notice I am saying class here not object. In the classic cookie and cookie cutter example you think of a class as the cookie cutter and the object as the cookie (the instance that the cookie cutter just stamped out). Now you can see why this idea is very foreign to a C# developer. It means classes can be extending at any time. This sounds a bit like extension methods but you will see as we dig deeper that there is more to it in Ruby.

Let’s first look at an example in Ruby:

class String
def blank?
self.size == 0
end
end


This is adding the method named “blank” which returns a boolean to the String class in Ruby. Now that is almost exactly like an extension method. We could get the same behavior in C# like this:



public static class StringExtensions
{
public static Boolean Blank(this string value)
{
return String.IsNullOrEmpty(value);
}
}


But there is another way to add behavior to a class in Ruby. This is via the method_missing method. Since all method calls in Ruby are really just messages there is a chance that a particular object won’t be able to handle the message it is given. In these cases Ruby will call a special “debugging method” if it cannot find a method with the specified name.



What does this mean for you? It means that you have a hook to handle any unknown method that gets called on your object at runtime. This is really cool but may not be totally clear without an example. The 7 Languages in 7 weeks book does about the best example I could think of so I will shamelessly steal that one to illustrate the point.



class Roman
def self.method_missing name, *args
roman = name.to_s
roman.gsub("IV","IIII")
roman.gsub("IX","VIIII")
roman.gsub("XL","XXXX")
roman.gsub("XC","LXXXX")

(roman.count("I") +
roman.count("V") * 5 +
roman.count("X") * 10 +
roman.count("L") * 50 +
roman.count("C") * 100)
end
end


Ok so what the heck is this thing doing? First it is defining the method_missing method on a class called Roman. It is then doing logic in the method_missing method to handle the name of the method that the user tried to call. Let’s look at what the call site would look like



puts Roman.X
puts Roman.XC


Now maybe it makes more sense. You can see someone tried to call a method named “X” on the Roman class. Instead of defining each permutation of possible Romans numerals as their own method we can simple catch the method_missing method and then do string based logic to figure out the method name and convert that to the correct integer value. Make sense? This is a core feature of Ruby and a very valuable concept to understand.



This is part of a larger series of posts inspired by the 7 languages in 7 weeks book. Use the7LangIn7Weeks tag to find the other related posts

Labels: , ,

Apr 4, 2012

Ruby for the C# developer–The Basics

I have been reading 7 languages in 7 weeks recently and I decided to to start a series of posts that focusing on learning the languages discussed in the book from the point of view of a C# / .NET developer.

So let’s get started. First we are going to start with Ruby. This is the one language in the book that I already had some familiarity with. I have read Design Patterns in Ruby by Russ Olsen and The Well Grounded Rubyist by David A Black. Both are really good books that focus more on the Ruby the language and less on Ruby in the context of Rails.

So let’s talk about the basics of the Ruby language.

Type System

Ruby has a strong dynamic type system. This means a few things

  1. You will receive an error if types collide. (This is the “strong” part)
  2. You will get this error at runtime not at compile time (This is the “dynamic” part)

Let’s look at some code examples of this. First C#

int foo = 10 + "bar";

In C# this results in a compile time error:



Now the same thing in Ruby


foo = 10 + “bar”

Since Ruby does type checking at runtime this will not cause any errors until you try to call it. Then you will receive the following error (also notice the lack of type information to the left of foo):



Classes


Ruby is an object oriented language with a single inheritance model like C#.


Let’s define a class in each language. First C#


public class Foo
{
public int Bar { get; set; }
public Foo()
{
Bar = 10;
}
}

This is a simple C# class that defines one public read / write property called Bar and has a constructor that sets Bar = 10. And now the same thing in Ruby


class Foo
attr_accessor :bar

def initialize()
@bar = 10
end
end

This is the same as the C# class above. The initialize method is the constructor for this class. The attr_accessor keyword defines a read / write property called bar (as opposed to att_reader which is a read only property). In the initialize method you see we reference the bar variable with an @ sign. This is the syntax for referencing instance variables in Ruby.


Interfaces vs Modules


Both Ruby and C# are single inheritance languages. To propagate non-similar behaviors to classes C# uses Interfaces whereas Ruby uses “mixin’s” via the module keyword.


Let’s first look at an Interface in C#


public interface IBaz
{
void DoSomething();
}

Here we define an interface called IBaz that defines one method DoSomething(). If we want to make sure our Foo class implements this “behavior” in C# we make Foo implement IBaz. This ensures that our object satisfies the IBaz contract (meaning we can call DoSomething() on an instance of foo)


public class Foo: IBaz
{
public int Bar { get; set; }
public Foo()
{
Bar = 10;
}

public void DoSomething()
{
//elided
}
}

Now lets look at how this is done in Ruby via “mixin’s”. First lets define our “mixin”


module IBaz
def do_something
#elided
end
end

Notice the subtle difference here? In the C# version we were simply defining a contract which our class must implement itself. In Ruby the implementation actually lives in the module (I named the module IBaz to keep with the C# example but that isn’t a Ruby naming convention).


And then to make sure our Ruby class can call the IBaz DoSomething we need to “include” it like this:


class Foo
include IBaz
attr_accessor :bar

def initialize()
@bar = 10
end
end

There is a key fundamental concept happening here. Modules can implement behaviors directly in Ruby. This means that a given type doesn’t implement “included” behaviors natively.


Duck Typing and Mutable Types


This is simply a fundamental difference between C# and Ruby. Many thanks to Arne for working through these thoughts with me.


Types in Ruby are mutable, even at runtime. This means that method dispatch cannot be resolved at compile time. This means that method dispatch is resolved at call time.


Call time method dispatch might sound similar to how the dynamic keyword works in C#. However, in Ruby there is a difference to be aware of.


The “method call” is simply a message that is sent to a given type instance. That message is then sent up the type hierarchy to see if there is any thing that can handle the message. This process involves looking at native methods on the type, “included” methods from mixins and checking the method_missing method.


In C# by using the dynamic type you can get past compile time checking of method call sites. However, at runtime if the object doesn’t contain the called method in it’s inheritance hierarchy the call will fail. In Ruby there is a language construct called method_missing that allows you to hook an unknown method call and do something with it.


This is where we will pick up in the next post. Open classes and the method_missing method


This is part of a larger series of posts inspired by the 7 languages in 7 weeks book. Use the 7LangIn7Weeks tag to find the other related posts

Labels: , ,