The Ruby Programming Language

St. John’s Linux User’s Group

www.slug.nf.net

October 16, 2003

Don Craig

Ruby Background

Scripting language created by Yukihiro Matsumoto (“Matz”) from Japan in 1993.

Borrows concepts from Perl, Smalltalk, Python, CLU and Lisp.

Designed from the ground up to be object-oriented and is very portable.

Features include exception handling, closures, iterators, garbage collection, threads and more.

Comprehensive support library.

Current version 1.8.0 (August 4, 2003).

Simple Input/Output

gets and puts provide line based I/O.

Some general notes about Ruby scripts:

No semi-colons at the end of statements.

No auto-conversion of strings to numbers.

raise generates an exception.

#{...} syntax provides interpolation.

Perl: print "Result: \"", chomp($str) x num, "\"\n";

str, num = gets, gets.to_i

raise "Need a positive number!" if num <= 0

puts "Result: \"#{str.chomp * num}\""

Ruby Variable Names

The language has the following rules regarding identifier names:

Constants/Classes begin with a capital letter.

Instance variables begin with @

Class variables begin with @@

Global variables begin with $

Local variables are lower-case, unadorned.

Like Java and Python, variables in Ruby act as “references” to objects which undergo automatic garbage collection.

Ruby Types

Ruby, like Python, is a dynamically typed language.

The type of a variable depends upon what was last assigned to it.

A variable can hold many different types during the lifetime of a script.

Ruby supports many types:

Array, Hash, String, Float, Integer, Regexp, Thread, Exception, IO, Time and more.

As with several other object-oriented languages, the Object class is the root of all types.

Class Hierarchy

Types are arranged in a hierarchy.

Object is an ancestor of Class!

Object

IO

String

Regexp

Numeric

Hash

Array

Module

Integer

Class

File

Fixnum

Bignum

Float

...

Numbers

Three main types of numbers:

Float for floating point numbers.

Fixnum for relatively small integers.

Bignum for relatively large integers.

Literal numbers can invoke methods!

1 + 1	# 2

-12.abs	# 12

a = 2 ** 29	# 536870912

a.class	# Fixnum

a = 2 ** 100000	# 9...6

a.class	# Bignum

1 / 0	# ZeroDivisionError

a = -1.0 / 0	# -Infinity

a.infinite?	# -1

12.34.floor	# 12

-12.34.floor	# -13

Math.cos(2 * Math::PI) # 1.0

Math.sqrt(152_399_025)

# 12345.0

076431 + 0xbeef_face % 0b1010

# 32025

12345.to_s	# "12345"

Strings

Ruby has a full-featured string class with numerous operations.

Like numbers, string literals are also objects and can have methods invoked on them directly.

Binary data or encoded text stored in strings can converted using unpack.

Note: [ ] operator returns Integer (not String).

str1 = String.new

str2 = ""

str3 = "hello world"

str4 = %q<'Don' "Quotey">

"a string".length	# 8

str2.empty?	# true

str3.sub(/hello/, "good-bye")

# good-bye world

str4.gsub!(/[aeiou]/) { |s|

s.upcase

} # str4 == 'DOn' "QUOtEy"

str4.swapcase![1]

# 100 (== 'd' in ASCII)

"%-20s%5.3f" % [str4, 1/3.0]

# 'doN' "quoTeY" 0.333

"SGkgVGhlcmU=".unpack("m")[0]

# "Hi There"

Arrays

Arrays contain a collection of objects.

The objects do not have to be of the same type.

Elements are accessible with an index, range, or offset/length pair.

Arrays support set operations as well (e.g. union, intersection, difference).

a1 = Array.new

a2 = []

a3 = [1, 'these', 4.3, [2, 'c']]

a4 = %w(these are some words)

a5 = [1, 2, 3, 4, 5, 6]

a1.push("are")

a1 << "some"

a1.unshift("these")

a1[3] = "words"

a1 == a4	# true

a5[3..-2]	# [4, 5]

a2[0]	# nil

a5[2,3]	# [3, 4, 5]

a4.include?('some')	# true

a5.pop	# 6

a5.reverse	# [5, 4, 3, 2, 1]

a5.length	# 5

a5.shift	# 1

a3 & a4	# ["these"]

Iteration

Many containers implement a method called each to iterate over the collection.

Integer classes define times and upto iterators.

Implemented by passing code blocks parameters to the iterator method. (More on this later...)

# The 'each' method takes a

# code block as a parameter

# and passes each element in

# the array to the code block

# via the 'x' parameter of

# the code block.

[1, 2, 3].each { |x|

puts "Value is #{x}"

}

# Value is 1

# Value is 2

# Value is 3

10.times { |x| print "#{x} " }

# 0 1 2 3 4 5 6 7 8 9

7.upto(10) { |x| puts 2**x }

# 128

# 256

# 512

# 1024

Methods

Methods are defined using def.

Method arguments may have default values.

Methods may have a variable number of arguments.

No keywords arguments.

Methods defined outside a class belong to the top level main object.

# Two parameters

def greeting(person, salutation = "Hello")

puts "#{salutation} #{person}!"

end

greeting("World")

greeting("Earthling", "Greetings")

# Variable number of parameters.

def var_args(*p)

puts "#{p.size} argument(s):"

p.each_with_index { |arg,num|

puts "(#{num+1}) #{arg.inspect}"

}

end

var_args "var", "args", 15.2, [1, 2, 3]

# Keyword arguments can be simulated

# using hashes.

def keyargs(p)

puts "#{p['greet']} #{p['who']}"

end

keyargs('greet'=>'Hi', 'who'=>'there')

keyargs('who'=>'mate', 'greet'=>'G\'day')

How Iterators Work

Iterators are implemented using a coroutine-like feature

When we call the iterator, a block of code is passed to the iterator method.

For each element in the collection, the iterator yields control back to the code block, passing the element via the code block’s parameter list.

class Array

# Note: Array doesn't

# actually implement each

# this way...

def each( )

i = 0

while i < self.size

yield

i += 1

end

end

...

end

...

[2, 3, 4].each

&block

self[i]

{|x| puts x}









Files

The File class (which is derived from the IO class) offers support for reading/writing files.

Files are automatically closed by Ruby during garbage collection.

File has several class methods for modifying and testing file attributes (e.g. chown, basename, mtime).

# Open an existing file and

# read its contents. File

# closed by garbage collector.

f = File.new('filename.txt')

while f.gets

puts "%3d %s" % [$., $_]

end

# Same idea as above, except

# that the file will be closed

# when the block finishes (don't

# need to wait for garbage

# collector to kick in).

File.open('filename.txt') { |f|

# nil record separator =>

#	'slurp' mode

puts f.gets(nil)

}

File.directory?('/etc') # true

File.mtime('/etc')

# Sun Oct 12 14:39:23 NDT 2003

Hashes

Hashes can be used to map objects to other objects.

Hashes can be initialized with key/value pairs in between {...}.

A default value can also be specified.

Hashes may also be queried using has_key? and has_value? methods.

h = {}; h.default = 0

# or h = Hash.new(0)

s = "how much wood could a

wood-chuck chuck if a wood-

chuck could chuck wood"

s.split(/[\s-]+/).each { |w|

h[w] += 1

}

h.has_key?("how") # true

h.delete_if { |word, count|

count <= 1 || word.length == 1

}

h.has_key?("how") # false

h.sort.each { |word, count|

puts "#{word}: #{count}"

}

# chuck: 4

# could: 2

# wood: 4

Regular Expressions

Regular expression objects are used for pattern matching.

The syntax is very similar to Perl 5.

A string can be tested using the match method.

Ruby also supports Perl’s =~ operator.

“Capturing parenthesis” may also be used.

if "Hello World" =~ /^h\w+ w\w+$/i

puts "Match!"

end

"hello world".gsub(/\b([a-z])/) {

$1.upcase

}	# "Hello World"

GRAD = %r{:([^:]+):/users/cs/grad}

pw = File.new('/etc/passwd')

pw.gets(nil).scan(GRAD) {

puts $1.split(/,/)[0]

}

s = "NOTE:Tuition:$2543"

m = /([a-z]+):(\$\d+)/i.match(s)

# m[0] = "Tuition:$2543"

# m[1] = "Tuition"

# m[2] = "$2543"

Ranges

Ranges are used to represent intervals or sequences.

The special operator === can be used to test for inclusion within a range.

Any type can be used for the range’s end-points as long as objects of that type are comparable and the type supports the succ method.

r1 = Range.new(1, 5)

r2 = 1..5

r3 = 0...5	# Exclusive range

r1.begin	# 1

r3.end	# 5

r3.exclude_end?	# true

# Convert ranges to arrays.

r2.to_a	# [1, 2, 3, 4, 5]

r3.to_a	# [0, 1, 2, 3, 4]

(1..5).to_a	# [1, 2, 3, 4, 5]

# Testing if a value lies

# inside a range:

("cat".."rat") === "dog"	# true

(64..256).include?(128)	# true

Iteration (again)

We can iterate over strings, ranges and file contents using the “code block as a parameter” technique described earlier.

map, find_all (and several other methods) are defined in the Enumerable module which many classes use as a “mix-in.”

"VMS".each_byte { |c|

print (c + 1).chr

} # WNT

gets(nil).each_byte { |x|

print "%08b" % x

}

hosts = '/etc/hosts'

LL = /^134\.153\.51\.\d\d?\b/

File.new(hosts).each_line { |l|

puts l if LL.match(l)

}

(0..100).find_all { |x|

x % 17 == 0 && x % 2 == 0

}

# [0, 34, 68]

"hello world".split.map{ |x|

x.capitalize }.join(" ")

# "Hello World"

Classes

Classes are logical units of abstraction.

Instances can be created by calling the class’s new method.

This automatically invokes the class’s initialize method (constructor).

A object “destructor” may be defined using the ObjectSpace module.

class Employee

def initialize(name, title)

if self.class == Employee

raise "Abstract class!"

end

# Instance variables are

# always prefixed with '@'

@name = name

@title = title

end

def to_s

"Name: #{@name}\n" +

"Title: #{@title}\n"

end

end

# Raises exception!

emp = Employee.new("blah", "blah”)

Inheritance

Ruby supports inheritance.

Access to base class methods is done using super.

A class’s to_s method is called whenever we need an instance in string form (e.g. puts)

Methods are “virtual” (dynamically bound) by default.

class Staff < Employee

def intialize(name, title)

super

end

end

# or we can just say:

# class Staff < Employee; end

class Faculty < Employee

def initialize(name, title, rsch)

super(name, title)

@research = rsch

end

def to_s

super + "Research: " +

@research.join(", ") + "\n"

end

end

department = [

Faculty.new("Rudy McGrath",

"professor", ["Algorithms"]),

Staff.new("Lucas Boswell",

"stenographer")

]

department.each { |x| puts x }

Exception Handling

Exception handling is performed using begin/rescue.

Ruby has several built-in exceptions. You can also define your own.

Exceptions can be thrown using raise.

Ruby also has a catch/throw feature for breaking out of nested constructs.

begin

f = File.new(gets.chomp)

while f.gets; print; end

rescue Errno::EACCES

$stderr.puts "Can't read it"

rescue Errno::ENOENT

$stderr.puts "Can't find it"

end

#	... or ...

begin

f = File.new(gets.chomp)

while f.gets; print; end

rescue Exception => err

$stderr.puts "Sorry: #{err}"

end

# Custom Exception...

class Oops < StandardError; end

begin

raise Oops, "Contrived!"

rescue Oops

puts "Error: #{$!}"

end

Accessing Instance Variables

Instance variables are always private.

Accessing instance variables must be done via methods.

Accessor/mutator methods can be added easily by using the attr_* methods.

The attr_* methods take “symbols” as arguments.

class Test

attr_reader :ro

# def ro; @ro; end

attr_writer :wo

# def wo=(val); @wo = val; end

attr_accessor :rw

# def rw; @rw; end

# def rw=(val); @rw = val; end

def intialize

@ro = "readonly"

@wo = "writeonly"

@rw = "read/write"

end

end

t = Test.new

t.ro = "hi"	# undefined method 'ro='

puts t.ro	# ok => "readonly"

t.wo = "hi"	# ok
puts t.wo	# undefined method 'wo'

t.rw = "hi"	# ok

puts t.rw	# ok => "hi"

Modules

Modules can be used to create a namespace.

Unlike classes, modules may not be instantied.

Modules are commonly used as mixins, which emulate multiple inheritance.

No warnings are generated when name clashes occur within a class and a module.

module ContractTeacher

SALARY = 3800.00

def teach

puts "teach, teach, teach!"

end

end

class Staff < Employee

include ContractTeacher

def reboot_windows

puts "kick, kick, kick!"

end

end

mike = Staff.new

mike.teach

class GradStudent

def write_thesis(topic)

puts "delay, delay, delay!"

end

end

don = GradStudent.new

class << don

include ContractTeacher

end

don.teach

Ruby Libraries

Ruby provides numerous libraries which can be used to extend its functionality.

Examples include libraries for various network protocols, GUIs (Tk), security (openssl), profiling and debugging.

require 'net/http'

require 'date/format'

DATE = /(#{Date::MONTHS.keys.join('|')})\s+\d+\s+\d{4}\s+\S+\s+\S+/i

INDICES = />(Dow|Nasdaq|S&amp;P 500)<.*?([\d\.,]+)/

html = Net::HTTP.get('finance.yahoo.com', '/')

puts DATE.match(html)[0]

html.scan(INDICES) { |idx,val| puts "%-8s:%9s" % [ idx.gsub(/&amp;/, "&"), val ] }

# Output:

# October 14 2003 11:23am ET

# Dow : 9,753.85

# Nasdaq : 1,931.03

# S&P 500 : 1,042.92

Class Methods and Variables

Ruby supports class (static) methods and variables.

Instantiation is not necessary when calling a class method or using a class variable.

Instance variables not accessible in class methods.

class Example

STR1 = "My name is:"

STR2 = "Most recent:"

@@shared = "<?>"

def initialize(name)

@@shared = @name = name

end

def Example.most_recent

puts "#{STR2} #{@@shared}"

end

def to_s

"#{STR1} #{@name}\n"

end

end

puts Example::STR1	# "My name is:"

Example.most_recent	# "Most recent: <?>"

e1 = Example.new("e1")

e2 = Example.new("e2")

puts e1	# "My name is: e1"

puts e2	# "My name is: e2"

Example.most_recent	# "Most recent: e2"

Object Persistance and Serialization

The Marshal module provides serialization methods which can be used to store and load a object’s state.

This mechanism can also be used to implement distributed Ruby programs.

Similar in spirit to the pickle feature in Python.

class Persist

def initialize(str, num)

@str, @num = str.chomp, num

end

def to_s

"@str=#{@str}\n" +

"@num=#{@num}\n"

end

end

d = Persist.new(gets, gets.to_i)

File.open("serial.dat", "w") { |f|

Marshal.dump(d, f)

}

# ...

File.open("serial.dat") { |f|

puts Marshal.load(f)

}

Method Dispatch and Introspection

You can invoke a class’s method by passing the name of the method as a parameter to the send method.

This makes tedious (and error-prone!) dispatch tables unnecessary.

Ruby offers several methods to let an object inspect itself and its own methods.

class Db

def open(name)

puts "Opening '#{name}' database..."

end

def update(field, value)

puts "Updating database..."

puts "#{field} => #{value}"

end

def help

Db.instance_methods(false).each{ |c|

puts "%8s %d args" %

[c, method(c).arity ]

}

end

def close

puts "Closing database..."

end

end

d = Db.new

print "$> "

while gets

begin

d.send(*split)

rescue Exception => err

$stderr.puts(err)

end

print "$> "

end

d.close

Proc Objects and Closures

When we pass a code block as a parameter, the receiving method converts the block to a Proc object.

The Proc object contains not only the code statements, but also the bindings (state) too!

The combination of statements and environment form a closure.

class Test

def initialize(&b)

@b = b

puts @b.class	# => Proc

end

def callit

@b.call	# =>'Hello'

end

end

def hello

str = "Hello"	# local var

Test.new {	puts str }

end

t = hello

# At this point, hello's str

# local variable still exists as

# part of the t object!

t.callit

Ruby Gotchas

There is no automatic conversion of strings to numbers, as there is in Perl and awk. On the plus side, the operators for strings and numbers are consistent, unlike Perl.

In most cases, variables serve as references. Modifications to an object that has been assigned to two different variables will be reflected by both.

false and nil are the only values treated as false in conditionals. Everything else (including 0) is true!

ruby -e 'printf "0 is %s\n", 0 ? "true" : "false"'

perl -e 'printf "0 is %s\n", 0 ? "true" : "false"'

Resources

www.ruby-lang.org

“Official” homepage.

Contains RAA (Ruby Application Archive).

www.rubygarden.org

Community oriented web site.

Contains RCRs (Ruby Change Requests).

www.rubycentral.com

Contains an HTML version of Thomas & Hunt’s helpful “pick axe book” and library reference (very useful).

2003-09-13T18:12:47