The first language we’ll cover in this course is Ruby. Ruby is a scripting language, and was designed by Yukihiro Matsumoto in the mid nineties.

There’s no clear cut line that delineates scripting languages from systems languages: it’s an arbitrary distinction. Scripting languages are really just those languages that have good out of the box support for writing scripts (simple utility programs). Ruby gets called a scripting language because it offers syntax and semantics that have sane defaults. For example:

  • There are no (explicit) types
  • Variables don’t need to be declared before they are used
  • You aren’t forced to write your code inside classes (contrast with Java)

Ruby’s a language that has lots of interesting features and utilities that are fairly well documented. I find myself routinely puzzling over how a piece of code works and reading about it fairly often. Usually, it only takes a few minutes of playing around in irb to figure out how a feature works and how to properly use it in your applications. The plethora of utilities may seem strange coming from languages that have more stringent coding styles, but most scripting languages optimize for being able to do common tasks succinctly.

“Hello, world!” in Ruby

The simplest Ruby program is this:

1 puts "Hello, World!\n"

Let’s open up an editor and work with this file. If we name it hello.rb in our folder, we can run it with:

ruby hello.rb

ruby is the name of the Ruby interpreter. An interpreter is a program that reads in your Ruby file and executes your commands. In our case, our file only has one statement: puts "Hello, World!\n". The program does what you’d expect: it prints out “Hello, World!” followed by a newline. puts is short for “put string,” and puts always prints a newline (even if one isn’t explicitly written in the string).

For executing Ruby programs, you use ruby, but you can also call it by using the shebang facility of your shell. Basically, if a file starts with #!, the shell will hunt for the program specified on the rest of the line and then invoke the rest of the script on that program. So if I wanted to, I could start my file with

#!/usr/bin/ruby

And then run my file. I don’t personally recommend this, because it means you’re using whatever Ruby is at that fixed location. Since I like to use RVM, I prefer to invoke my scripts with ruby manually.

IRB

IRB (Interactive Ruby) is a command line shell for interacting with Ruby. You can start it up by typing irb. You can then play with different commands and try out various things. It’s useful for prototyping or playing around with a library, but brittle for writing large files (e.g., because you have to copy and paste).

Basic Ruby syntax

Comments

Begin with a # sign and go on for the rest of the line:

1 # Here's a comment
2 x = 1

Variables are implicitly declared

Meaning that they don’t need to be explicitly declared (as in C / Java).

If / Else / Conditionals / Loops

Work the same as in other languages:

1 if (i == 0)
2   "it's 0!"
3 else
4   "it's not!"
5 end

There’s also unless, and until, which allow us to write concise code in certain cases (but I rarely use them).

Loops include while, until, and for. But there’s usually a better way to loop in Ruby using iterators.

The nil object

nil is nothing. It’s like null in Java or NULL/0 in C. It’s the default. It’s an element of NilClass.

Truthiness and Falsiness

In Ruby conditionals, the only things considered false are nil and false: everything else is true

In languages that don’t have static types, a boolean conditional could evaluate to any type:

1 if "dog" then
2   1
3 else 
4   2
5 end

Ruby will warn me, but I will still get 1 as the result.

In short, Ruby evaluates the true branch when the guard returns any value that isn’t false or nil. In the parlance of our times, this is called truthiness or falsiness.

You can read more about it here

Methods

  • Start with the def keyword
  • Followed by name of method and argument list
  • Must be terminated with the end keyword

For example, here’s a definition of the factorial function as a Ruby method.

1 def fac(i) 
2   if (i == 0)
3     1
4   else
5     i * fac(i-1)
6   end
7 end

Note that in the above example, I needed two end keywords: one for the if and one for the method. You can’t leave either out.

Return values

Note that you don’t need an explicit return statement. The last thing computed in the method is implicitly returned. You can always call return anyway, it’s not incorrect. Calling return is helpful for “breaking out” of the function.

To follow up with our previous example, we could have written:

1 def fac(i) 
2   if (i == 0)
3     return 1
4   end
5   i * fac(i-1)
6 end

Note that the else branch isn’t used here.

Stylistic notes

There are a few stylistic conventions that you should consider following:

  • Names of methods that return a boolean should end with a ?. These are sometimes called predicates. E.g., the method Fixnum.zero? of the Fixnum class checks to see if a number is zero.

  • Methods that change an object’s state should end in a bang (!). E.g. .sort! sorts arrays in place:

1 x = [1,3,2]
2 puts x.sort.to_s # [1,2,3]
3 puts x.to_s      # [1,3,2]
4 x.sort!
5 puts x.to_s      # [1,2,3]

Core Ruby concepts

Now that we’ve gotten the logistics out of the way, let’s talk about what makes Ruby different from languages you might have learned previously.

To quote this page:

In Ruby, everything is an object.

Ruby is dynamically typed

This means that a value can have different types at different points in time. In C or Java, if you declare a variable x, it must always be an (e.g.,) integer. By contrast, this is perfectly acceptable in Ruby:

1 x = 1
2 puts x
3 x = "hello"
4 puts x

Or even this:

1 puts "Type 1 or 0"
2 x = gets
3 if (x == "1\n") then
4   y = "hello"
5 else 
6   y = 23
7 end
8 puts y

In the second example, the type of y changes depending on which branch I take. In one branch, it’s a string, and on the other it’s a number.

Now, it would be false to say that Ruby has no types. Every object has a type at runtime. You can look up an object’s type by calling its .class method. Let’s do that by changing the previous example:

1 puts "Type 1 or 0"
2 x = gets
3 if (x == "1\n") then
4   y = "hello"
5 else 
6   y = 23
7 end
8 puts y.class

Note that when I call .class on 23, I get Fixnum. Fixnum is a class that represents arbitrarily sized numbers in Ruby. You can look up its class reference here

Tip: Looking up Ruby classes

When you need to know more about a Ruby class, just look it up on http://ruby-doc.org/, or just Google it.

Just like everything else in Ruby, even classes are objects! This is potentially surprising, especially if you’re learning Ruby coming from Java.

The interpreter is interacting with your code

In most languages, code is a static blob. It gets compiled, turned into code that looks like this:

Java bytecode diagram

In other words, there’s a stratification between your source code, and the object files emitted by the compiler (and subsequently run by your processor or e.g., the JVM).

But in Ruby, this is not the case. The interpreter reads in and runs code as the code is evaluated. Code can even be loaded after evaluation starts.

Example: turning numbers into matrices

You’ve probably read about matrix arithmetic in some previous classes. Let’s say we want to represent matrices as lists of lists, so the 2x2 identity matrix would be represented as:

[ [1, 0],
  [0, 1] ]

We might write a bunch of code that needs to turn a Fixnum into a matrix. One way we might do this is simply to extend the Fixnum class:

 1 class Fixnum
 2   def to_matrix
 3     identity = Proc.new do |i| 
 4       v = []
 5       i.times do |x|
 6         v[x] = Array.new(i,0)
 7         v[x][x] = 1
 8       end
 9       v
10     end
11     identity.call(self)
12   end
13 end
14 puts 2.to_matrix.inspect
15 # [[1, 0], [0, 1]]

Note that here, we’ve stored a code block. Code blocks can be represented as objects that are members of the Proc class. You can
think of this class as representing the code block by wrapping it in an object with only one method: call. (There are other methods too.)

Although we might want to do it, we can’t simply store a code block in a variable: Ruby will yell at us with a syntax error. Remember, everything in Ruby must be an object. To be consistent with this world view, we wrap the code block in a Proc object.

The really cool thing here is that we’ve opened up the Fixnum class. In Java, C++, and other compiled languages, classes have one point at which they’re defined. In Ruby, we can open classes at will. Think of it like this: when the compiler sees

class X

It loads up class X in memory, and waits for you to start adding methods to it using the def keyword. This is a good way to think about it, because it hits home the reality that the interpreter is manipulating the representation of classes at runtime. So, think of the interpreter as having a memory of the “current” class that’s being manipulated.

Some helpful Ruby classes

For each of these classes, it’s helpful to just skim the documentation. Even if you just read over the set of methods available to you, you might figure out a shortcut for something that you find yourself repeating in a project. Chances are — if you’re doing something repetitively — there’s likely a better way. Feel free to ask on Piazza: learning the shortcuts is key to getting a feel for Ruby code.

Array

Arrays are used all over the place. Unlike those in Java and C, arrays don’t have to be manually (re)sized. You can add and remove elements as you wish. Arrays are sometimes colloquially called lists.

Arrays are so different than in other languages that it’s helpful not to get too caught up in the terminology. For example, in Ruby, Arrays can contain objects of heterogeneous types:

1 x = [1,"boo",2.4]

Like C and Java, however, they’re accessed with the same syntax, and start at zero:

1 x[0] # 1
2 x[1] = "foo"

Arrays grow automatically:

1 x = [1]
2 x[4] = [2]
3 # x contains [1,nil,nil,nil,2]

You can iterate through arrays in a few ways:

  • Using a while loop with an explicit indexing iterator (same as in C).

  • Using the for..in syntax

  • Using .each with a block:

1 y = 0
2 [1,2,3].each { |x| y = y+x }

Ranges

1 x = 1..10  # Represents the range 1, 2, ..., 10
2 x = 1...10 # Represents the range 1, 2, ..., 9

Tip: Dont confuse 1..n and [1..n]

The first is a range, the second is an array that contains one element (the range 1..n). I make this mistake all the time when I accidentally try [1..n].each and get a type error.

Ranges represent ranges of values. They’re not the same as arrays, i.e., 1..4 is not the same as [1,2,3,4], because one is an instance of class Range and the latter is of class Array. But you can call methods and compute on them. Because the preferred way to iterate through collections is with blocks, you end up using them the same:

1 >> (1..10).each { |x| print x, ' ' }
2     1 2 3 4 5 6 7 8 9 10 => 1..10

Symbols

A symbol is like a string, but meant for internal use from your code. They’re good for specifying things like configuration options. Symbols are prefixed with the : character, as in :length:

1 myBox.resize({:length => 42, :width => 21})

Unlike strings in Ruby, symbols are interned. This is a fancy way of saying, there is only one of them in the whole environment. If you think about Java, each time you create a string literal, it’s a distinct object. So for example:

1 >> "h".object_id
2 => 70147784781420
3 >> "h".object_id
4 => 70147784769360 # <-- Different!

But there’s only one unique object representing each symbol:

1 >> :h.object_id
2 => 365928
3 >> :h.object_id
4 => 365928         # <-- Same!

The ruby method Object.equal? compares objects for referential equality, so "h".equal?("h") is false, but :h.equal?(:h) is true.

Hash

Hashes are association tables, their name comes from their implementation. Like arrays, they can store heterogeneous key, value pairs.

You update and index them with their keys, similarly to arrays:

 1 >> x = {:k=>:m,"l"=>"p"}
 2 => {:k=>:m, "l"=>"p"}
 3 >> x[:k]
 4 => :m
 5 >> x[:g] = 2
 6 => 2
 7 >> x.each do |x,y| print x, ' ', y, "\n" end
 8 k m
 9 l p
10 g 2
11 => {:k=>:m, "l"=>"p", :g=>2}

Note that they are iterated using a code block that has two parameters. Hash.each calls the block for each key, value pair in the Hash.