Click here to view the entire IronRuby via C# series
In my last post about learning Ruby via IronRuby and C# we talked about variables. We covered local variables, global variables, and both instance and static variables in Ruby. In this post we are going do delve a bit more into Ruby classes and start looking at defining methods and passing around parameters.
But first, let me start off with one quick thing so that you can start to play around with Ruby a bit on your own. And that one thing is output. In C# we would do this:
Console.WriteLine("My test value");
And in Ruby, all we do is this:
puts "My test value"
Simple, now that you can spit things out to the console, lets get on with the meat of this post.
To start it off, I am going to define a quick C# class with a constructor and a method:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public Person(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; } public string FirstAndLastName() { return String.Format("{0} {1}", FirstName, LastName); } }
Here you see that we have a Person class that has two properties and initializes those properties in the constructor. Then we have a method that returns a first and last name together using String.Format. You might be surprised just how similar this looks in Ruby:
class Person attr_accessor :first_name attr_accessor :last_name def initialize(first_name, last_name) self.first_name = first_name self.last_name = last_name end def first_and_last_name "#{@first_name} #{@last_name}" end end
So, you can see that in Ruby we have our attribute accessors instead of the properties in C#. Then we have a specialized method called “initialize”. This is essentially a Ruby constructor. It is what Ruby calls in order to initialize a new instance of a class. You don’t actually have to call this method though, as you’ll see in a second. Next you see that we have a method called “first_and_last_name” that returns our first and last name appended together. First notice that we don’t specify a return type, this is because everything in Ruby returns a value. Either that value is nil (Ruby’s null) or it is a valid value. Also notice that there is no return statement, Ruby takes any statement on a line by itself as a return. You can also see that we are using our variables directly in the string, this is a common theme in Ruby where they provide syntactic sugar for doing common tasks like this.
In order to consume these classes you would just use the “new” operator in C# like this:
var person = new Person("Ted", "Smith");
And In Ruby you would use the new method, like this:
person = Person.new("Ted", "Smith")
Another useful features in C# is the “params” keyword when passing variables to methods. Your method would look something like this:
public string Concat(params string[] items) { string result = ""; foreach (string item in items) { result += item; } return result; }
In Ruby this is also available, by use of the asterisk.
def concat(*items) result = "" items.each do |item| result += item end result end
The Ruby method does virtually the same thing, and is arguably easier to read. In the C# version we are using a “foreach” statement and looping through each item in our items array. In Ruby the same thing happens, we are given an array of items and we use the “each” method to perform the concatenation on each item. Each may not look like a method, but it is, and this is due to the fact that parentheses are generally optional in Ruby.
So, if parentheses are optional, then we could have created our Person object up above like this?
person = Person.new "Ted", "Smith"
And the answer is yes, you could. But what do I mean about them being “generally” optional? Well, there are cases, such as when you are nesting method calls that not having parentheses would lead to ambiguous situations. Take for example this method call:
person.concat(person.first_and_last_name, "test")
Now, look at that without the parentheses:
person.concat person.first_and_last_name "test"
Yep, ambiguous. In fact, Ruby tries to give the parameter to person.first_and_last_name, which takes no arguments, and so we get an error. So, if the parentheses are optiona, are the commas optional? Nope. In fact, if we do this:
person = Person.new "Ted" "Smith"
We get an error telling us that the person initialize class expects 2 arguments and we only passed one. One? Does it pass it as an array? Nope. In Ruby, the “+” sign in string concatenation is optional! In fact, there are a few other ways to concatenate strings, but I’ll just tell you to stick with the plus operator.
So, we mentioned above that “each” is just a method on the “items” array. So, if “each” is a method, then what is its parameter? Well, the entire thing from “do” to “end” is the parameter. In Ruby these are called blocks, and you can define your own methods that take them. In Ruby there are two forms of blocks, above you see on type, but we can also define another type that, as a C# developer, may look a bit more familiar to you:
items.each { |item| result += item }
See, now you can probably start to see a bit better how this translates to C#. Think of it as an action delegate, and lets define this extension method real quick:
public void Each<T>(this IEnumerable<T> list, Action<T> func) { foreach (T item in list) { func(item); } }
Okay, now we can use this on our array in C# like this:
items.Each(delegate(string item) { result += item; });
Hmmm, C# is maybe borrowing a bit here? In fact, we could even use the Aggregate function (also called Fold) and then the Lambda syntax would make it look even more similar. But don’t let blocks confuse you with lambdas, because blocks are not lambdas. Don’t fear though, because Ruby also has lambdas!
Well, I’m gonna stop there for the evening. In the next post I am going to discuss control structures in Ruby a bit (if statements, loops, etc…) and then probably get back to blocks a bit, since they are used quite heavily in Ruby.
I hope you enjoyed the post!
Loved the article? Hated it? Didn’t even read it?
We’d love to hear from you.
I really enjoy this series. One of the things I’ve been meaning to do is learn some IronRuby and perhaps throw together a test RoR site – just to get a feel for it. Your post will definitely be a reference point when I make the time to do that.
Good work, Justin.
Great series, please keep them coming!
Nice, Justin. I’ve always learned new languages in the context of others. Seeing your examples contrasting with C#, my native toungue these days, makes the information fly into my brain. Can I haz more?
@Brian, Denny Thanks guys!
@Kevin More? Yes you can, Kevin, yes you can. 🙂
inside the initialize method, you would typically set your instance variables using the @ syntax, such as "@first_name = first_name", instead of "self.first_name = first_name", but both are valid. in case you’re wondering, the @first_name variable is defined when you call "attr_accessor :first_name".
when it comes to iterators and arrays, there are many ways to do anything. your concat method can be shortened to:
def concat(*items)
items.inject(""){ |result, item| result += item }
end
@jacob Thanks, I will note in the future that "self" is not often used. I just wanted to show the "self" versus "this" in C#.
And thanks for the shortened "concat" method. I was trying to keep the method as simple as possible for those who have not written Ruby before. I wonder why they chose to call it "inject" instead of the standard "fold". Just like in C# they called the method "aggregate", but at least to me that makes more sense than "inject".
The string template section could do with some more explanation.
I don’t like class attributes and so left out that part, but then I was unable to get the string method working at the end with the "#{}" syntax. Googling around for "ironpython string template" doesn’t seem to find a recognizable explanation for its syntax.
I’ll keep looking around.
OK. I figured it out as:
class Person
@firstName
@lastName
def initialize(first, last)
@firstName = first
@lastName = last
end
def asString
"#{@firstName} #{@lastName}"
end
end
person = Person.new("John", "Doe")
puts person.asString
Tom,
Be aware that instance variable (like your @firstName) defined/used outside of a method have a different meaning than those used inside a method.
So, the 2nd and 3rd line of your class definition is creating 2 instance variables in the scope of the instance of the class definition; which is not the same as when you set/get variable of the same name inside your instance method.
So, you’ve created two variables called @firstName (and 2 called @lastName), one stores data per person object, and the other is stored with the class, essentially.
Here’s a brief demonstration (sorry, I had to change the casing from firstName to first_name, as it feels more rubyish to me):
class Person
@first_name = "Jacob"
@last_name = "Swanner"
def initialize(first, last)
@first_name = first
@last_name = last
end
def to_s
"#{@first_name} #{@last_name}"
end
def self.first_name
Person.instance_eval("@first_name")
end
def self.last_name
Person.instance_eval("@last_name")
end
end
person = Person.new "John", "Doe"
puts person # => John Doe
puts Person.first_name # => Jacob
puts Person.last_name #=> Swanner
Thank you, Jacob.
So I’m confused now. Do I leave-off the @? on the 2nd and 3rd lines? I’ll give that a try.
I love variable naming conventions. I’m more influenced by Smalltalk and so use lowerCamelCase. I suppose I’ll eventually adopt Ruby idioms. When in Ruby, do as the Rubians.
Ok. A quick look at some Ruby sites tell me instance variables have ampersands in-front of their names. Unfortunately, it looks like the "#{}" syntax really wants methods inside.
OK.
If I only have the following, my code works as expected.
class Person
def initialize(first, last)
@firstName = first
@lastName = last
end
def asString
"#{@firstName} #{@lastName}"
end
end
p1 = Person.new("john", "doe")
p2 = Person.new("jane", "doe")
p1.asString
"john doe"
p2.asString
"jane doe"
I’m curious, though, how I may declare instance variables at the top of the class so browsers of its source code may easily discover its instance variables.