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
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.
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):
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)
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.