Learning Ruby via IronRuby and C# Part 6

Click here to view the entire IronRuby via C# series

Welcome back for part 6 in my learning Ruby via C# series. In the last entry we discussed arrays in Ruby and C#. We saw how Ruby arrays were actually more like Lists or Collections in C#. They are able to be modified, appended, resized, searched, sorted, etc… In this entry we are going to discuss an equally powerful data structure, the hash or associative array. The hash is close to the Dictionary or Hashtable classes in C#. We are going to use the Dictionary class in this entry since it is the more often used class.

So, in C# if we wanted to declare a Dictionary and populate it with a few keys and values we would do something like this:

var jobs = new Dictionary<string, string>();
jobs.Add("Developer", "Jimmy Dean");
jobs.Add("Senior Developer", "John Smith");
jobs.Add("Network Engineer", "Bob Franklin");

In Ruby hashes are a first class member of the language, so they have their own syntax for declaring them. One way to declare a hash looks like this:

jobs = {}
jobs["Developer"] = "Jimmy Dean"
jobs["Senior Developer"] = "John Smith"
jobs["Network Engineer"] = "Bob Franklin"

But you could also do it all in one line by separating the key and the value with the “=>” operator:

jobs = {"Developer" => "Jimmy Dean", "Senior Developer" => "John Smith", "Network Engineer" => "Bob Franklin"}

Sweet. But in Ruby, symbols are the preferred way of representing the keys in hashes. It would be similar to defining an enumeration in C# and using that for the Dictionary key:

public enum Jobs
{
    Developer = 1,
    SeniorDeveloper = 2,
    NetworkEngineer = 3
}

var jobs = new Dictionary<Jobs, string>();
jobs.Add(Jobs.Developer, "Jimmy Dean");
jobs.Add(Jobs.SeniorDeveloper, "John Smith");
jobs.Add(Jobs.NetworkEngineer, "Bob Franklin");

In Ruby we would set it up like this:

jobs = {}
jobs[:Developer] = "Jimmy Dean"
jobs[:SeniorDeveloper] = "John Smith"
jobs[:NetworkEngineer] = "Bob Franklin"

So now that we know how to build up our hashes, how do we get our entries back out? We probably know how to do this in C#:

Console.WriteLine(jobs[Jobs.Developer]);

That would send “Jimmy Dean” out to the console. Since we know how to do it in C#, we already know how to do it in Ruby!

puts jobs[:Developer]

Very cool. Ruby hashes also support the “each” method just like the Ruby array. In C#, we would loop through each item in the dictionary like this:

foreach (KeyValuePair<Jobs, string> job in jobs)
{
    Console.WriteLine(String.Format("{0} {1}", job.Key, job.Value));
}

We could also cheat and use my “Each” extension method that I defined:

public static void Each<T>(this IEnumerable<T> list, Action<T> func)
{
    foreach (T item in list)
    {
        func(item);
    }
}

And then our C# code looks like this:

jobs.Each(j => Console.WriteLine(String.Format("{0} {1}", j.Key, j.Value)));

Looks better and pretty much what our Ruby version looks like:

jobs.each { |key, value| puts "#{key} #{value}" }

So now we have seen how to build up, access, and then iterate through our hashes. But what is the coolest use for hashes in Ruby? Well, we mentioned it in an earlier post, and that is method parameters! It allows us to have a variable number of named parameters. As C# developers we are not used to having named parameters, but if you think about it, if you name parameters then their order is not important. So, if you pass hashes for parameters then you can change method signatures without worrying about breaking older code. Lets look at a method real quick:

def print_names(params = {})
    first_name = params.fetch(:first_name, "John")
    last_name = params.fetch(:last_name, "Doe")
    job = params.fetch(:job, "No Job")
    
    puts "#{first_name} #{last_name} Job: #{job}"
end

You can see that we are pulling our variables out of the hash that is passed into the method, providing default values. The default value is the second parameter of the “fetch” method. So, we could call this method like this:

print_names(:first_name => "Justin", :last_name => "Etheredge", :job => "Programmer")

Or like this:

print_names(:job => "Programmer", :last_name => "Etheredge", :first_name => "Justin")

Or this:

print_names(:first_name => "Justin", :job => "Programmer")

Or even like this:

print_names

Are you starting to see the flexibility of this method of parameter passing? If later on we need to add a parameter for number of years in position:

def print_names(params = {})
    first_name = params.fetch(:first_name, "John")
    last_name = params.fetch(:last_name, "Doe")
    job = params.fetch(:job, "No Job")
    number_of_years = params.fetch(:number_of_years, 0)
    
    puts "#{first_name} #{last_name} Job: #{job} Number of years: #{number_of_years}"
end

All of our old code will still work. Now this isn’t all ponies and gumdrops though, you can see that it makes calling methods much longer affairs. It also doesn’t help that our method’s only parameter is “params = {}”. That doesn’t exactly provide us anywhere to start for calling the method. You have to look into the method to see what you need to pass, and here you see that we are fetching the values up front and putting them into local variables. But what if the method was long and didn’t do this up front? Then you might have references to the hash strewn all throughout your method and you would have to hunt for them. The only other option is to have good documentation for your methods, which obviously won’t always happen. Suffice to say, that this can make a method very hard to call in the hands of a poor programmer, but then again, Ruby is all about giving sharp tools to sharp people.

Another flexibility that this gives you is the ability to do different things based on which items are in the hash. Since Ruby does not support method overloading, it makes it hard to have multiple methods perform different functions based on their parameters. With hashes though you can check if certain items exist in the hash using the “has_key?” method and then perform different actions based on what is passed.

As you have seen, hashes are incredibly useful data structures in Ruby. When you combine them with methods they can give you flexibility that most languages just don’t have. There are many more things you can do with the Ruby hash, but you’ll have to go check out the Ruby hash docs yourself.

I hope that you have found this little tutorial helpful!

Be Sociable, Share!

7 comments

  1. Two comments:

    1. C# can also place the Dictionary initialization on one line, using collection initializers:

    var jobs = new Dictionary<string,string> {
    {"Developer", "Jimmy Dean"},
    {"Senior Developer", "John Smith"},
    {"Network Engineer", "Bob Franklin"},
    };

    <shameless-plug>
    I put collection initializers to great use in my command-line parsing library, NDesk.Options: http://www.ndesk.org/Options
    </shameless-plug>

    2. C# 3 allows you to "fake" named parameters on the caller’s side by using anonymous types:

    Foo (new { P1 = V1, P2 = V2, P3 = V3 });

    ASP.NET MVC uses this; see http://weblogs.asp.net/scottgu/archive/2007/11/13/asp-net-mvc-framework-part-1.aspx
    in the "Rendering Approach 1: Using Inline Code" section:

    Html.ActionLink(category.CategoryName, new { action="List", category=category.CategoryName })

    The "callee" (implementation) side isn’t quite as nice, as it needs to use reflection to see if a specified "parameter" is present, though this could be cleaned up with an extension method:

    public static T Fetch<T>(this object self, string property, T defaultValue)
    {
    PropertyInfo pi = self.GetType().GetProperty(property);
    if (pi == null) return defaultValue;
    return (T) pi.GetValue(self, null);
    }

    Allowing:

    void PrintNames (object args)
    {
    firstName = args.Fetch ("first_name", "John");
    lastName = args.Fetch ("last_name", "Doe");
    job = args.Fetch ("job", "No Job");
    }

    Though, as you mention, this isn’t ideal as there’s ~no documentation about what parameters the method actually accepts; a more C#-ish equivalent would be to introduce an additional type to contain the "named" arguments. Kevin Pilchie has discussed this before:

    http://blogs.msdn.com/kevinpilchbisson/archive/2007/11/30/coding-styles-bools-structs-or-enums.aspx

    e.g.: DisplayUser(user, Console.Out, new DisplayUserOptions { Email = true, PhoneNumber = false });

  2. @Jonathan As always, your comments are great. Yes, I should have shown the collection initializers, and I had not thought about using anonymous types to fake named parameters. Interesting. I’m going to have to check out Kevin’s post.

  3. I don’t know if it’s a good thing or a bad thing that you’re posting to your blog on the weekend now. I thought you had ribs to cook. :)

    Love the Ruby tutorial. Keep it coming.

    Kevin

  4. I really enjoy reading these tutorials. I have messed around with ruby a bit but this is really getting me interested in exploring it further.

  5. @Kevin The ribs cook themselves while I write on my blog. :) Although I did end up over cooking them this time.

    @Justin Thanks! It is always good to hear positive feedback. I hadn’t decided whether or not to continue with the Ruby tutorial, but it looks like I am going to.

  6. Hi,

    These are one of the best Tutorials and comparison, i have ever come across in recent times.IronRuby and Ruby deserves a special attention in .Net world and you are perhaps the first filling that gap of knowledge.

    Great efforts by you and you are doing best you can. Since you are putting your maximum efforts here, i would like to suggest a bit more on this tutorials.

    It would be really great that you may consider, explaining LINQ with IronRuby, since these would be really helpful to go ahead with an ORM that is going to stay for longer time.

    Ruby is best with Active Record and I presume many would love to go ahead with IronRuby and LINQ in .Net world.

    Pl. extend this tutorials with LINQ and IronRuby as a part of this or you may start a new Tutorial heading for this topic.

    Thanks

  7. @SoftMind Sorry I didn’t see your comment until now. That is an excellent suggestion, and I was planning on looking at IronRuby and Linq in just a bit. Thanks!

Leave a comment