Computer Science Canada

[Ruby-tut] Method to my madness

Author:  wtd [ Tue Jun 29, 2004 10:28 pm ]
Post subject:  [Ruby-tut] Method to my madness

I've alluded to methods in my previous Ruby tutorials, but I haven't yet explained what a method is. In other object-oriented languages, a method is (more or less) a function or procedure which exists as part of a class. However, I haven't yet mentioned classes, so why am I using the word "method"?

In "Hello, Ruby!" I stated that everything in Ruby is an object. It's true. All this time I've been working inside the Object class without knowing it. This is just one of the ways Ruby implements the "do what I mean" philosophy of its creator, Yukihiro Matsumoto.

For now though, you can forget that I mentioned any of that, as I'm not yet ready to introduce classes. Just remember that methods in Ruby are analogous to functions or procedures in other programming languages. Both are means of collecting a set of related statements or expressions under one meaningful name. They allow code to be easily reused.

For instance, in the continuing saga of our very simple converation with Ruby, I have code which accomplishes this conversation. If I wanted to repeat that conversation again, I'd have to retype all of that code. Instead, though, I could wrap it all up in a method and just call the method when I want to converse with Ruby.

code:
def converse_with_ruby
   puts "Hello, Ruby!"
   print "I'm "
   name = gets.strip

   statement = case name
      when ""
         "fine, ignore me!"
      when /Calvin|Linus|Sid/
         "Haaaaa haaaaa!"
      else
         "Hello #{name}"
   end

   puts "Ruby says, \"#{statement}\""
end


Now to call it...

code:
converse_with_ruby


Ruby kindly does not require that parentheses be included when they're not necessary to make it clear what's happening in the code.

We've hardly taken advantage of methods, though. We still have one big chunk of code that's all run together. We can't use just a little bit of that without manually retyping the code.

Instead let's have a method for each step in the process.

code:
def say_hello_to(recipient)
   puts "Hello, #{recipient}"
end


Notice the use of an argument in this method. Using the argument, we can pass information into the method cleanly. Ruby can also accomodate default values for arguments. With a default argument the method can be called without providing any arguments and Ruby will assume the default value.

code:
def say_hello_to(recipient = "Ruby")
   puts "Hello, #{recipient}"
end


We need a method to get a name from standard input. We'll pass in a string to be used as a prompt.

code:
def get_name(prompt = "I'm ")
   print prompt
   gets.strip
end


Notice that there's no need for "return". It's there if you want it, but by default, the last expression in a method will be returned.

Now, we need a method to formulate Ruby's response.

code:
def rubys_response(recipient)
   case recipient
      when ""
         "fine, ignore me!"
      when /Calvin|Linus|Sid/
         "Haaaaa haaaaa!"
      else
         "Hello #{name}"
   end
end


For Tony, I present the one line version. Wink

code:
def rubys_response(recipient) case recipient when "" then "fine, ignore me!" when /Calvin|Linus|Sid/ then "Haaaaa haaaaa!" else "Hello #{name}" end end


Now, a method which prints Ruby's response:

code:
def ruby_quoted(message)
   puts "Ruby says, \"#{message}\""
end


Now we put it all together.

code:
def converse_with_ruby
   say_hello_to "Ruby"
   ruby_quoted rubys_response get_name
end


It's pretty simple, but it's one of the big concepts to grasp.

Oh, wait...

I said everything in Ruby was an object, right? Well, I lied. Sorta. About the only things that aren't are methods.

Yet it's handy sometimes to be able to pass around methods like variables. For this, Ruby has the idea of proc objects. Commonly we create these using the lambda keyword. For instance, say we want a proc object that takes one argument and returns that object times two.

code:
times_two = lambda { |x| x * 2 }


Or if you prefer the alternate syntax:

code:
times_two = lambda do |x| x * 2 end


So, now there are two ways we can call this code later on.

code:
result = times_two.call(21)


Or

code:
result = times_two[21]


But probably even more useful than this behavior is the ability to pass blocks to methods. "Block" is the term given to pretty much everything except the "lambda" keyword.

Let's consider the problem of calculating tax, and then putting the results into a string. The end result should look something like "Sales tax on $100 = $7".

Blocks in the argument list of a method should be preceded by an amphersand. This is omitted when referring to the argument inside themethod, though.

code:
def calculate_tax(name, amount, &block)
   tax_amount = block.call(amount)
   "#{name} on $#{amount} = #{tax_amount}"
end


Then to call the method...

code:
calculate_tax("Sales tax", 100) { |x| x * .07 }


And once more, for different results, showing what's possible.

code:
calculate_tax("Sales tax", 100) do |x|
   if x < 100
      x * .07
   else
      "Way too much!"
   end
end


Or, if we use one of these calculations a lot...

code:
cheap_skate = lambda do |x|
   if x < 100
      x * .07
   else
      "Way too much!"
   end
end

calculate_tax("Sales tax", 100, cheap_skate)


Yield to my wisdom...

The other way of having blocks like these with methods is to use the "yield" keyword to, well... yield... a variable or variables to a block of code, then return the output of that code.

code:
def calculate_tax(name, amount)
   "#{name} on $#{amount} = #{yield amount}"
end

calculate_tax("Sales tax", 100) do |x|
   if x < 100
      x * .07
   else
      "Way too much!"
   end
end


The usefulness of these becomes wonderfully apparent as one delves deeper into Ruby. They make it easy and simple to do complex things.

Author:  wtd [ Tue Jun 29, 2004 10:51 pm ]
Post subject:  Addendum: argument tricks

In the above tutorial I showed how arguments can be passed to Ruby methods. There is, however, a trick I failed to mention.

A variable number of arguments can be passed to a method by prepending an argument with an asterisk. Those arguments will then be "slurped" into an array.

code:
def sum(*integers)
   # imagine some code here
end

sum(1,2,3,4,5,6)


More on arrays and such later. Smile


: