Ruby Tutorial: Introduction to Ruby

Editors Note: This first entry in a Ruby tutorial series has been written by fellow MacResearcher Chinmoy Gavini. Chinmoy has also graciously agreed to allow MacResearch to mirror a number of his older tutorials on our site for permanent archiving. We'll be moving them over the coming days and weeks to the Tutorials section of the site.

A bit of background

Ruby is a dynamically typed object-oriented scripting language from Japan created by Yukihiro Matsumoto("Matz"). Why do we need another object-oriented scripting language when Python already exists? Matz says he wanted a scripting language more object-oriented than Python. A example I can think of is: the length of an array in Ruby is found by ary.length where ary is the name of the Array object, but in Python, len(ary) gives the length.

For small to medium size scripts, one can use a procedural style of programming in Ruby like in C. For larger programs, however creating classes and using objects is more convenient.

Diving In

In this first tutorial, I will cover the basics of Ruby. If you are interested in more before the next article in this series, you can check out my introductory Ruby screencasts hosted at ShowMeDo or the Ruby In Twenty Minutes tutorial on the official Ruby site. I will introduce Ruby object-oriented programming in the next tutorial. Meanwhile, for a nice overview of object-oriented programming, please refer to Drew's Cocoa for Scientists tutorials.

Installing Ruby

Unfortunately, the Ruby distribution that is bundled with Mac OS X 10.4 doesn't have proper GNU Readline support. This means you can't access command history in the Interactive Ruby(irb) tool. To fix this, you can try MacPorts,which used to be called DarwinPorts, for installing the latest version of ruby, or manually install Ruby using Dan Benjamin's instructions at Hivelogic. I think Leopard will include a correctly installed version of Ruby as well as the Ruby on Rails framework.

MacPorts

You can install MacPorts by downloading and running the installer. Then, these instructions might be of help in installing Ruby(you don't need Ruby on Rails, etc, if you don't plan on doing web programming).

Introduction

Here's the traditional "Hello World" program: puts "Hello World".You can run this program by typing in irb in the Terminal and typing puts "Hello World" at an irb prompt. puts automatically appends a new line to the string you are sending to the output, while print doesn't. Also note that parentheses are optional. We could just as well have written puts("Hello World") . I prefer to put parentheses in for two or more parameters

Interactive Ruby(irb)

IRB is a great tool that lets you interactively explore Ruby and test existing objects or your own objects.Here's a sample irb session started by typing in irb in the Terminal. The words including and following '#' are just Ruby comments.


irb>puts "Hello"
"Hello"=>nil
irb> a = [] #new 
=>[]
irb> a << "a" << "b" #appending strings "a" and "b" to an array
=>["a","b"]
irb>if(a.class == ["2",["3","4"]].class)
irb>print "True"
irb>end
True=>nil 

You can access previous commands by using the up arrow key. irb has lots of other neat features, which I will mention later.

What's that nil doing in "Hello"=>nil? Well, everything in Ruby is an expression, and not a statement. So puts "Hello" simply returns nil. This lets us chain methods like: foo.to_s.to_a, (this code converts foo to a string and then to an array, assuming foo has those methods defined). Ruby allows arbitrary precision arithmetic within the memory limits of the machine. Try typing in 30000**30. Yes, the ** operator is the same as the FORTRAN exponentiation operator.

Running Scripts

Here's a script version of the Hello World program. You can put this code in your favorite text editor and save it as helloworld.rb:

 
#!/usr/bin/env ruby 
puts "Hello World!"

You can run this on Unix systems like Mac OS X by first typing in chmod +x helloworld.rb, which gives the file executable permission, and then typing in ./helloworld.rb once you have changed to the directory where you put your script in.
The line #!/usr/bin/env ruby is a special comment that tells the shell how to find the Ruby interpreter.

Subroutines

Here a basic subroutine(function) that outputs whatever is sent to it and outputs "hello" by default. Start irb and type:


def sendoutput(outputstring="hello')
  puts outputstring
end

Invoke this function by entering sendoutput("goodbye"), then entering sendoutput("goodnight") and then entering sendoutput. You should see "goodbye" or "goodnight" or "hello", respectively. You could also have put the function in a script as follows:


#!/usr/bin/env ruby
def sendoutput(outputstring="hello")
  puts outputstring
end

if __FILE__ == $0 
 sendoutput("hullo")
end

I enclosed my "test" code inside if __FILE__ == $0 to make sure it doesn't get run when loaded into irb. This is a common idiom and is especially useful for Ruby libraries. Notice that Ruby does not use {} for scoping. {} is used for blocks instead. Also, indenting is strongly encouraged but not required, unlike Python. Instead, the keyword end denotes the end of a scope

Scoping Examples:


x = 2
if(x == 2)
  puts "x is #{x}"
elsif (x > 3) #elsif not a typo!
  puts "x is not 2"
end

#I will talk about classes next time 
class Foo
  def initialize
   #code here
  end
end 

Arrays,Hashes,Strings

Arrays and Hashes(a.k.a dictionaries,associative arrays,maps) are very useful data structures we can easily use in our programs. Here are a few examples with comments. You can simply use irb for this sample code.

Arrays


ary = [] #ary = Array.new also works 
ary << 5 <<  6 
puts ary[-1] 

top = ary.shift 

#ary.to_s converts the array into a string.
#Actually, any class that defines to_s can be converted into a string.  
str = ary.to_s 

bottom = ary.pop 


#printing the elements of the array
#also see Blocks and Iterators section below.

ary.each {|elem| puts elem} 

Hashes

 
h = {} #h = Hash.new(0) would default unmapped values to 0 
h['Steven'] = "234-233-8979"
h['Alice'] = "356-903-5792" 

h.each_key {|key| puts "The phonenumber for #{key} is #{h[key}"}
puts "Bob does not exist in the directory" if !h.has_key?("Bob") 

Strings

There is no character type like in C. 'A' is a string and so is 'apple'. The String is a powerful class with a lot of methods. Enter ri String
in the Terminal to find out more. Double quoted strings can be used to interpolate more objects than single-quoted strings. For example, puts "5 + 3 is #{5+3}"prints out "5 + 3 is 8"

Nil

Nil means "nothing". It is not false, although it has false semantics.

For example,


s = nil
s.length #throws exception 
s = []
s.length #returns 0

if s.empty?
print "s is nil"
else
print "s is not nil"
end 

This last if statement would print "s is not nil" assuming you entered s = [] previously.

Some differences from Perl

Strings and numbers cannot be interchanged between one other as in Perl. For example, you cannot do print 2+3+"3"+"4" and expect to get 12. You can, however do print 2+3+"3".to_i+"4".to_i and expect to get 12. You can also do something similar to eval("7+5") to evaluate an arithmetic expression.

You don't need semicolons at the end of statements, unless you are combining two expressions on the same line. An @ in front of an identifier symbolizes array context in Perl, but in Ruby, an @ in front symbolizes instance variable of an object. In Perl, scalar variables are all prefixed with $. In Ruby, only global variables are prefixed with $.

You cannot use uninitialized variables like in Perl. In Perl, uninitialized variables have undef, but Ruby throws an exception if you try to use uninitialized variables

Blocks and Iterators

In Ruby a block is a chunk of code that is very similar to a function. A block is basically a "coroutine which the main method can pass control back and forth to"(paraphrased from the book Programming Ruby). Here is the classic example of a block which, along with each replaces the need for a traditional loop for iterating over the array. Generally speaking, for any class which provides a custom each method and includes the Enumerable module, one can iterate over the data of interest and perform other iterative computations without using loops.


a = [2,3,4,1,3,4]
s = 3
a.each {|elem| puts "The next element is #{elem}}

Note that elem is a "block parameter". A block can also use the local variables that are located in the scope of its calling method(in this case, it could use s). For multi-line blocks, or for more clarity, the do..endversion is used instead. As a matter of style, some programmers use the {..} version when they care about the return value of the block and use do..end otherwise. Here's another example using methods which accept a block. This code reads a bunch of numbers from specified files in the current directory, and performs a cumulative scalar multiply on the vector repeatedly and outputs the result:

 
#!/usr/bin/env ruby
#name: multiply.rb


vec = [1,2,3,4,5]
ARGF.each do |line| 
    vec.collect! {|i| i *= line.to_f}
end
vec.each {|item| puts item} #prints the arrays contents

The collect! method applies the block once for each element of self. self is analogous to the this pointer in C++(more on self later). In this case self is vec. The exclamation point indicates something serious will happen--usually when the receiver(vec) will be modified. ARGF is a nice special variable. Among other things it is used to store the strings passed in as commandline arguments.

To run this code, create two sample input files input.txt and inputtwo.txt and fill them with numbers by typing in numbers separated by newlines(Enter key) in each file. Don't hit Enter after the last number in each file. Then, run this script at the Terminal by typing in chmod +x multiply.rb, and then typing in ./multiply.rb input.txt inputtwo.txt(keep it all on one line).

Next Time

Next time I will talk more about blocks, classes, and I/O, including getting your own methods to handle blocks

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

minor correction

Under Hashes, the comment

#h = {0} would default unmapped values to 0

should be # h = Hash.new(0) would default unmapped values to 0

Sorry about that,

Chinmoy.

Henry Ford-style arrays

Ugh--Henry Ford-style arrays again. Any array you want as long as it starts with zero. When will language designers learn to make their languages abstractable to the user's problems in this regard? This restriction is deadly for technical computing, at least. And they are unsafe arrays as well--if you use a negative index it just counts from the far end of the array instead of issuing an error.

Re: Henry Ford-style arrays

Don't worry about this too much.
One solution would be to subclass the existing Array. Note that the following code is a simple proof-of-concept and has not been unit tested or refactored. Since this feeds into classes, I will probably make a few comments on this code as well as comments on using the profiler to compare its speed to a plain array.


class SaferArray < Array
  def [](index)
    if(index.class == 5.class)
     raise ArgumentError,"Index must be positive",caller  if index <0
     return self.at(index-1)
    end
    
    if index.class == ((0..1).class) #FIXME still uses old version
      self.values_at(index)
    end 
  end

  def []=(index,value)
    self.fill(value,index-1,index-1)
  end 
end

if __FILE__ == $0 
  ary = SaferArray.new([1,2,3])
  x = ary[3]
  puts x
  ary[3] = 2
  puts ary[3]
  begin
  puts ary[-1]
  rescue ArgumentError 
    puts "Caught negative index error"
  end

end

For that wacky negative indices business, Perl is probably the one that started it, and Python has it as well. While many "Perlisms" like some magic variables are being deprecated in Ruby, I don't think negative indices will go away any time soon.

Regards,

Chinmoy

Re: Henry Ford-style arrays

Thanks--I'll be interested to see the results of any speed tests.

Jerry

Re: Henry Ford-style arrays.

It is very straightforward to profile ruby code using the profiler library(comes with the Ruby standard library). After profiling the code from my earlier post I changed the implementation of SaferArray to be a simple wrapper over the regular array. Here is the code and some sample profiling results(very informal and not comprehensive):


#!/usr/bin/env ruby -w
class SaferArray
include Enumerable
  def initialize(ary)
    @ary = ary
  end

  def [](index) 
    raise ArgumentError,"Index must be positive",caller if index < 0
    @ary[index-1]
  end

  def []=(index,value)
    raise ArgumentError, "Index must be positive", caller if index <0
    @ary[index-1] = value
  end

  def each
    if block_given?
      @ary.each {|elem| yield elem}
    end
  end

  #some array functionality most probably omitted for now
  #but we get collect,inject,etc. for free because we defined 
  #each
end


if __FILE__ == $0 

  tstary = SaferArray.new([])
  Profiler__::start_profile 
  100000.times do |i|
	  randidx = rand(100000)
	  tstary[randidx] = 3
  end 
  Profiler__::stop_profile
  Profiler__::print_profile($stdout)

 tstarytwo = Array.new([])
 Profiler__::start_profile
 100000.times do |i|
    randidx = rand(100000)
    tstarytwo[randidx] = 3
 end
 Profiler__::stop_profile
 Profiler__::print_profile($stdout)

end

Results:
 %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 41.12    10.65     10.65   100000     0.11     0.17  SaferArray#[]=
 20.27    15.90      5.25        1  5250.00 25900.00  Integer#times
 14.63    19.69      3.79   100000     0.04     0.04  Fixnum#<
 12.82    23.01      3.32   100000     0.03     0.03  Kernel.rand
  6.83    24.78      1.77   100000     0.02     0.02  Array#[]=
  4.32    25.90      1.12   100000     0.01     0.01  Fixnum#-
  0.00    25.90      0.00        1     0.00     0.00  Integer#to_int
  0.00    25.90      0.00        1     0.00 25900.00  #toplevel
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 38.41     4.64      4.64        1  4640.00 12080.00  Integer#times
 30.79     8.36      3.72   100000     0.04     0.04  Array#[]=
 30.79    12.08      3.72   100000     0.04     0.04  Kernel.rand
  0.00    12.08      0.00        1     0.00 12080.00  #toplevel

Without any negative indices checks,(above, it didn't matter whether I used exception handling or not, it seemed to me that the comparison was expensive)


 %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 38.14     7.56      7.56   100000     0.08     0.11  SaferArray#[]=
 36.73    14.84      7.28        1  7280.00 19820.00  Integer#times
 11.60    17.14      2.30   100000     0.02     0.02  Array#[]=
  7.11    18.55      1.41   100000     0.01     0.01  Fixnum#-
  6.41    19.82      1.27   100000     0.01     0.01  Kernel.rand
  0.00    19.82      0.00        1     0.00     0.00  Integer#to_int
  0.00    19.82      0.00        1     0.00 19820.00  #toplevel
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 59.01     7.17      7.17        1  7170.00 12150.00  Integer#times
 31.93    11.05      3.88   100000     0.04     0.04  Kernel.rand
  9.05    12.15      1.10   100000     0.01     0.01  Array#[]=
  0.00    12.15      0.00        1     0.00 12150.00  #toplevel

I will discuss more of this stuff later. The speed in any case might be appalling compared to other languages, but there are ways to compile Ruby into bytecode (for example see YARV). YARV only works from Ruby 1.9 upwards though. But it results in faster execution and supports kernel threads--as far as I know. I know JRuby already supports kernel threads via the Java virtual machine.

Regards,

Chinmoy.

Ruby for dense stellar system simulation

I came across this website that has many articles about using Ruby for "modelling dense stellar systems". The discussion itself might be a bit on the beginner's side, but it has lots of code examples that might be useful to people.