Ruby Programming/Ruby basics

← Introduction to objects | Data types →


As with the rest of this tutorial, we assume some basic familiarity with programming language concepts (i.e. if statement, while loops) and also some basic understanding of object-oriented programming.

Dealing with variables

edit

We'll deal with variables in much more depth when we talk about classes and objects. For now, let's just say your basic local variable names should start with either a lower case letter or an underscore, and should contain upper or lower case letters, numbers, and underscore characters. Global variables start with a $.

Program flow

edit

Ruby includes a pretty standard set of looping and branching constructs: if, while and case

For example, here's if in action:

a = 10 * rand

if a < 5
  puts "#{a} less than 5"
elsif a > 7
  puts "#{a} greater than 7"
else
  puts "Cheese sandwich!"
end

[As in other languages, the rand function generates a random number between 0 and 1]

There will be plenty more time to discuss conditional statements in later chapters. The above example should be pretty clear.

Ruby also includes a negated form of if called unless which goes something like

unless a > 5
  puts "a is less than or equal to 5"
else
  puts "a is greater than 5"
end

Generally speaking, Ruby keeps an if statement straight as long as the conditional (if ...) and the associated code block are on separate lines. If you have to smash everything together on one line, you'll need to place the then keyword after the conditional

if a < 5 then puts "#{a} less than 5" end 
if a < 5 then puts "#{a} less than 5" else puts "#{a} greater than 5" end

Note that the if statement is also an expression; its value is the last line of the block executed. Therefore, the line above could also have been written as

puts(if a < 5 then "#{a} less than 5" else "#{a} greater than 5" end)

Ruby has also adopted the syntax from Perl where if and unless statements can be used as conditional modifiers after a statement. For example

puts "#{a} less than 5" if a < 5
puts "Cheese sandwich" unless a == 4

while behaves as it does in other languages -- the code block that follows is run zero or more times, as long as the conditional is true

while a > 5
  a = 10*rand
end

And like if, there is also a negated version of while called until which runs the code block until the condition is true.

Finally there is the case statement which we'll just include here with a brief example. case is actually a very powerful super version of the if ... elsif... system

a = rand(11) # Outputs a random integer between 0 and 10

case a
when 0..5
  puts "#{a}: Low"
when 6
  puts "#{a}: Six"
else
  puts "#{a}: Cheese toast!"
end

There are some other interesting things going on in this example, but here the case statement is the center of attention.

Writing functions

edit

In keeping with Ruby's all-object-oriented-all-the-time design, functions are typically referred to as methods. No difference. We'll cover methods in much more detail when we get to objects and classes. For now, basic method writing looks something like this (save the following code in a file called func1.rb):

# Demonstrate a method with func1.rb
def my_function( a )
  puts "Hello, #{a}"
  return a.length
end

len = my_function( "Giraffe" )
puts "My secret word is #{len} characters long"

now run the script:

$ func1.rb
Hello, Giraffe
My secret word is 7 characters long

Methods are defined with the def keyword, followed by the function name. As with variables, local and class methods should start with a lower case letter.

In this example, the function takes one argument (a) and returns a value. Note that the input arguments aren't typed (i.e. a need not be a string) ... this allows for great flexibility but can also cause a lot of trouble. The function also returns a single value with the return keyword. Technically this isn't necessary -- the value of the last line executed in the function is used as the return value -- but more often than not using return explicitly makes things clearer.

As with other languages, Ruby supports both default values for arguments and variable-length argument lists, both of which will be covered in due time. There's also support for code blocks, as discussed below.

Blocks

edit

One very important concept in Ruby is the code block. It's actually not a particularly revolutionary concept -- any time you've written if ... { ... } in C or Perl you've defined a code block, but in Ruby a code block has some hidden secret powers...

Code blocks in Ruby are defined either with the keywords do..end or the curly brackets {..}

do
  print "I like "
  print "code blocks!"
end

{
  print "Me too!"
}

One very powerful usage of code blocks is that methods can take one as a parameter and execute it along the way.

[ed note: the Pragmatic Programmers actually want to point out that it's not very useful to describe it this way. Instead, the block of code behaves like a 'partner' to which the function occasionally hands over control]

The concept can be hard to get the first time it's explained to you. Here's an example:

$ irb --simple-prompt
>> 3.times { puts "Hi!" }
Hi!
Hi!
Hi!
=> 3

Surprise! You always thought 3 was just a number, but it's actually an object (of type Fixnum) As it's an object, it has a member function times which takes a block as a parameter. The function runs the block 3 times.

Blocks can actually receive parameters, using a special notation |..|. In this case, a quick check of the documentation for times shows it will pass a single parameter into the block, indicating which loop it's on:

$ irb --simple-prompt
>> 4.times { |x| puts "Loop number #{x}" }
Loop number 0
Loop number 1
Loop number 2
Loop number 3
=> 4

The times function passes a number into the block. The block gets that number in the variable x (as set by the |x|), then prints out the result.

Functions interact with blocks through the yield. Every time the function invokes yield control passes to the block. It only comes back to the function when the block finishes. Here's a simple example:

# Script block2.rb

def simpleFunction
  yield 
  yield
end

simpleFunction { puts "Hello!" }
$ block2.rb
Hello!
Hello!

The simpleFunction simply yields to the block twice -- so the block is run twice and we get two times the output. Here's an example where the function passes a parameter to the block:

# Script block1.rb

def animals
  yield "Tiger"
  yield "Giraffe"
end

animals { |x| puts "Hello, #{x}" }
$ block1.rb
Hello, Tiger
Hello, Giraffe

It might take a couple of reads through to figure out what's going on here. We've defined the function "animals" -- it expects a code block. When executed, the function calls the code block twice, first with the parameter "Tiger" then again with the parameter "Giraffe". In this example, we've written a simple code block which just prints out a greeting to the animals. We could write a different block, for example:

animals { |x| puts "It's #{x.length} characters long!" }

which would give:

It's 5 characters long!
It's 7 characters long!

Two completely different results from running the same function with two different blocks.

There are many powerful uses of blocks. One of the first you'll come across is the each function for arrays -- it runs a code block once for each element in the array -- it's great for iterating over lists.

Ruby is really, really object-oriented

edit

Ruby is very object oriented. Everything is an object -- even things you might consider constants. This also means that the vast majority of what you might consider "standard functions" aren't floating around in some library somewhere, but are instead methods of a given variable.

Here's one example we've already seen:

3.times { puts "Hi!" }

Even though 3 might seem like just a constant number, it's in fact an instance of the class Fixnum (which inherits from the class Numeric which inherits from the class Object). The method times comes from Fixnum and does just what it claims to do.

Here are some other examples

$ irb --simple-prompt
>> 3.abs
=> 3
>> -3.abs
=> 3
>> "giraffe".length
=> 7
>> a = "giraffe"
=> "giraffe"
>> a.reverse
=> "effarig"

There will be lots of time to consider how object-oriented design filters through Ruby in the coming chapters.