Blocks and Closures
Although there were things like event handlers in .NET 1.x, you were forced to define full methods to attach to events when you wanted them handled. This led to the creation of a lot of methods that really only existed because the framework demanded it.
.NET 2.0 introduced the concept of anonymous delegates. They function in much the same way as blocks do in Ruby:
irb(main):001:0> h = {:foo=>'bar', :hi=>'there'}
=> {:foo=>"bar", :hi=>"there"}
irb(main):002:0> h.each_key {|k| puts k}
foo
hi
=> {:foo=>"bar", :hi=>"there"}
irb(main):003:0> h.each {|k,v| puts "#{k}: #{v}"}
foo: bar
hi: there
=> {:foo=>"bar", :hi=>"there"}
As you can see, the syntax for blocks in Ruby is quite terse: Use curly braces to open and close the block, and use the
|x,y| syntax to designate the variables passed to the block.
Blocks in Ruby also act like closures, just as anonymous delegates do in .NET 2.0. This means they have access to values in their enclosing scope, even after that scope has exited. Here's a simple closure that multiplies together several values:
irb(main):004:0> n = [5, 6, 10]
=> [5, 6, 10]
irb(main):005:0> t = 1
=> 1
irb(main):006:0> n.each { |i| t *= i }
=> [5, 6, 10]
irb(main):007:0> t
=> 300
You can even store references to blocks to be used later:
irb(main):008:0> t = 1
=> 1
irb(main):009:0> f = lambda { |i| t *= i }
=> #<Proc:0x0818a7b0@(irb):9>
irb(main):010:0> n.each &f
=> [5, 6, 10]
irb(main):011:0> t
=> 300
Methods
Method definitions in Ruby are simpler than in .NET, because types are not specified:
irb(main):001:0> def greet(name)
irb(main):002:1> puts "Hello, #{name}!"
irb(main):003:1> end
=> nil
irb(main):004:0> greet "Reader"
Hello, Reader!
=> nil
irb(main):005:0> greet 42
Hello, 42!
=> nil
Ruby performs something called "Duck Typing": if it walks like a duck and quacks like a duck, it must be a duck. You don't ask it "Are you a duck?"—you just assume it's a duck and call
quack on it. Even if you were expecting a duck, anything that can quack is allowed to join in the party.
Note that the
greet method definition didn't set any limits on the types of objects that can be passed in, because it just prints them—and everything in Ruby supports printing, by virtue of the
to_s method on Object, which will convert the object into a string. The end result is that you can
greet anything.
Here's another example:
irb(main):006:0> def print_len(item)
irb(main):007:1> puts "Len = #{item.length}"
irb(main):008:1> end
=> nil
irb(main):009:0> print_len "Reader"
Len = 6
=> nil
irb(main):010:0> print_len [1, 4, 9]
Len = 3
=> nil
irb(main):011:0> print_len 42
NoMethodError: undefined method <span class="pf">'</span>length' for
42:Fixnum
from (irb):7:in <span class="pf">'</span>print_len'
from (irb):11
The
print_len method now does more than before: it calls the
length method on the object passed to it. So the requirement for something to be passed to
print_len is that it has a
length method that you can call with zero parameters. Passing a string or an array works because they have an appropriate
length method; but passing a number doesn't work, because they don't have a
length method.
To write a method like this in .NET, you'd probably need to invent an IHaveLength interface and use that as your parameter type. For classes invented before you created your interface, you're out of luck, or you end up creating type converters. The whole thing spirals out of control quickly as the strong typing system gets in your way.
On the other hand, at least if you had IHaveLength, you understand what the requirements of the method are. Since types act as a form of documentation, you need an alternative in dynamic languages, which means you will be more reliant on things like written documentation and unit tests to help discern how some class or method is meant to be used.
Ruby supports default argument values:
irb(main):012:0> def repeat(val, times = 5)
irb(main):013:1> val.to_s * times
irb(main):014:1> end
=> nil
irb(main):015:0> repeat "hi"
=> "hihihihihi"
irb(main):016:0> repeat "hi", 3
=> "hihihi"
irb(main):017:0> repeat 10, 3
=> "101010"
Notice that there's no
return before
val.to_s * times. Methods in Ruby always return the result of the last evaluation, unless explicitly told to return a value, so the
return keyword is just seven extra characters you don't need to type.
Ruby supports variable arguments:
irb(main):018:0> def add(*values)
irb(main):019:1> result = 0
irb(main):020:1> values.each {|x| result += x}
irb(main):021:1> result
irb(main):022:1> end
=> nil
irb(main):023:0> add 1, 2, 3, 4, 5
=> 15
Ruby packages up the variable arguments into an array. You can then access the values that are passed and (in this case) add them together.
Method and Variable Naming Conventions
Methods in Ruby start with a lowercase letter, and can contain letters, numbers, and the underscore.
Method names that change the underlying object end with an exclamation point (
!). For example, the
upcase method on strings returns the uppercase version of the string, but leaves the original string alone. In contrast the
upcase! method actually changes the underlying string.
Methods that answer questions (returning Boolean values) have names ending in a question mark (
?).