CS 3723
  Programming Languages  
  Ruby Tutorial   


The main Ruby references: In these notes and elsewhere my principal reference is
  • Programming Ruby: The Pragmatic Programmer's Guide (PPG), by David Thomas, Addison-Wesley, 2001, 608 pages. This covers Ruby 1.7. Available online at the link above. Even this is a dense and complex reference which should be fine for this course.

    A newer and much larger 3rd edition (2009, 864 pages, covers Ruby 1.9, $31.30) is available for sale at Amazon.com There are now a large number of Ruby books out, and this one is not designed for beginners.

A second possible reference is:

  • Free Tutorials


Illustrating the Ruby Way:
    The way that can be named is not the true way. -- Lao Tse, Tao Te Ching   (See The Ruby Way by Hal Fulton)

    Here is a Ruby program from the PPG that resembles its counterpart in Perl. The program prints all lines in the input stream that contain the word "Ruby". Notice that while and if statements (along with most others) end with end and do not make use of {} blocks, as in Perl. This code uses the "hidden" global variable $_, and for this (and other) reasons is increasingly out of favor with Ruby programmers.

      
      while gets   # assigns each input line to $_ by default
        if /Ruby/  # matches against $_ by default
          print    # prints $_ by default
        end
      end
      

    Here is another version in Ruby about which PPG says: "The 'Ruby way' to write this would be to use an iterator." (Iterators, a more general construct than in Perl, are discussed three sections down on this page.)

      
      ARGF.each { |line|  print line if line =~ /Ruby/ }
      

    PPG says: "This used the predefined object ARGF, which represents the input stream that can be read by a program."


One-liner Introduction:
    % perl -e 'print 5+2;   $x = "hi"; print $x,"\n";'
    7hi
    % ruby -e 'print 5+2;   $x = "hi"; print $x,"\n";'
    7hi
    % ruby -e 'print 5.+(2); x=%q|hi|; print "#{x}\n"'
    7hi
    %

    Here the -e option on either Perl or Ruby causes the quoted string following to be treated as one line of program source to be interpreted. The first two inputs show that this is a program that both Perl and Ruby execute, both producing output 7hi. The third line is more "Ruby-like" and includes the strange features:

    • The expression 5.+(2), is using the method +, with parameter 2 on the integer object 5, using . as the means to select the method from the object. This form is called the dot syntax and is the usual way to invoke methods. For operators, Ruby permits the normal-looking way also.

      In Perl, the expression 5.+(2) is legal and just means to add floating point 5 and integer 2. For this reason, Ruby requires a digit after the decimal point of any floating point constant, since otherwise 5.+(2) would be ambiguous in Ruby.

    • The form %q is a short-hand way to insert quotes. (It takes the place of Perl's qq.)

    • String interpolation is done in Ruby with #{}, rather than Perl's ${} or @{}.

    • Semicolons are almost never used at the end of a statement in Ruby, though they are optional, just as in Perl.


Prefixes and Scope:

  • Prefixes: In Ruby these are used to indicate scope and not data type as in Perl. In Ruby, any variable name can hold a reference to any type of data. Here are some of Ruby's rules:
    • A variable starting with a lower-case letter (and with no prefix) is local in scope.
    • A $ prefix means a variable that has global scope.
    • A variable starting with an upper-case letter (and with no prefix) is a constant and has class-wide scope. (Constants often use all upper-case letters.)
    • Class names also start with an upper-case letter. (It turns out that class names are also constants.)
    • The prefix @ is used for a variable belonging to a particular object, that is, an instance of a class.
    • Variables starting with @@ belong to a whole class, that is, they have class scope.
    • In Ruby, the prefixes @ and @@ have nothing to do with arrays (as they do in Perl).

  • Predefined Variables and Constants:
    • Ruby has many of the same special "scalar variables" that start with $ and are present in Perl. These have been included for convenience and because many people are familiar with them.
    • For example, the variable $_ can be used in Ruby in much the same way as in Perl (it holds the current line), but Ruby has alternatives to its use. We will mostly avoid these variables.


Ruby Syntax:
    Ruby's syntax initially looked very strange to me, but now I understand it better. Ruby allows needless tokens to be left out, and Ruby programs usually don't bother with these extras, writing in a succinct style. Here is a page illustrating this: Illustrative programs. Ruby has a semicolon at the end of a statement, but a newline can work for a statement terminator if the line is syntactically complete. So normally one either finishes a statement at the end of the line, or leaves something "hanging" such as an operator. A backslash at the end also means the statement continues to the next line.

    In a similar way, for many of Ruby's constructs, such as the if-then-else-end or the while-do-end, a keyword at the end of a line, such as then or do is optional and usually omitted. (This is also illustrated in the program above.)


Loops and Iterators (not the same things):


Arrays:
    Ruby supports arrays (indexed by integers) as well as hashes (indexed by any object; see next section). Unlike Perl, the special prefixes @, @@, or $ have nothing to do with arrays. Elements of an array can hold a reference to any object. Arrays will grow as needed to support insertion of new elements, even beyond the current bounds of the array. The class Array has a large number of flexible methods which are illustrated in the following program.

    Ruby example showing arrays
    #!/usr/local/bin/ruby
    x = 3
    a = [14, 3.1415926, 'kitty', "Tell me #{x} times", [2, 3, 4]]
    print a[3]                   # Prints:  Tell me 3 times
    a[1, 2]                      #  [3.1415926, 'kitty']
    a[4]                         #  [2, 3, 4]
    print a[4][1]                # prints: 3
    
    b = Array.new(5, "A")        # b is ["A", "A", "A", "A", "A"]
    b[3] = 13                    # b is ["A", "A", "A", 13, "A"]
    b[6] = 8                     # b is ["A", "A", "A", 13, "A", nil, 8]
    b.each {|x| print x, " "}    # prints: A A A 13 A nil 8
    print b.length               # prints: 7
    print b[-1]                  # prints: 8, the last element
    print b[-4]                  # prints: 13, 4 back from last element
    
    c = Array.new                # new empty array
    print c.length               # prints: 0
    c[2] = 47
    for x in 0...c.length        # prints: nil nil 47
      print c[x], " "
    end
    for x in 0...c.length do print c[x], " " end        # same thing
    
    m = %w( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec )
    m.each {|x| print x, " "}    # prints: Jan Feb Mar Apr ...
    m.collect! {|x| x.downcase + ':'}  # applies {} block to each elt
                                 # downcase turns caps to lowercase
                                 # ! at end means change object itself
    m.each {|x| print x, " "}    # prints: jan: feb: mar: apr: ... 
    


Larger Example:
    This example shows a function (sqrt2) defined within a class in one file. Then in a second file the function is used to calculate pi using 25 terms of the following infinite product (which must be solved for pi):

    Notice the statement require "math2" at the beginning of the program to use the sqrt2 function, which makes the class containing the function available to the other program. The code resembles Java.

    Notice also that the way the middle function is structured, the variable m cannot be a local variable inside the function pi, but, if it's not a parameter, it must be global, and so must be written as $m.

    The file math2.rb could have consisted of just the definition of the function sqrt2, not embedded in a class. Then in the file piproduct.rb, the code could just use the function sqrt2 directly without instantiating a class and without the variable $m.

    Code for sqrt function
    File: math2.rb
    Code to calculate pi
    File: piproduct.rb
    Output of run
    (parameter on command line)
    #!/usr/local/bin/ruby
    class Math2
       def sqrt2(x)
          x = x.to_f # to float
          if x < 0   # if undefined
             nil
          elsif x == 0 # sqrt(0) = 0
             0.0
          else
             # use better init value
             y = x
             # use Newton's method
             yold = y + 1.0
             while (y-yold).abs > 
                   0.1e-14
                yold, y =
                y, x/(2*y) + y/2
             end
             y # return answer
          end
       end
    end
    
    #!/usr/local/bin/ruby
    require "math2"
    
    def pi(n)
       p = 2 # final product
       b = 0 # each denom
       for i in 1..n
          b = $m.sqrt2(2 + b)
          t = 2.0/b # term
          p *= t
          if i%2 == 0
             print  " i: ", i,
              ", t: ", t,
              ", p: ", p, "\n"
          end
       end
       p
    end
    
    $m = Math2.new
    x = ARGV[0].to_i
    print "pi: ", pi(x), "\n"
    
    % piproduct.rb 25
     i: 2, t: 1.08239220029239
        p: 3.06146745892072
     i: 4, t: 1.00483857237631
        p: 3.13654849054594
     i: 6, t: 1.0003012720413
        p: 3.14127725093277
     i: 8, t: 1.00001882507178
        p: 3.14157294036709
     i: 10, t: 1.00000117654968
        p: 3.1415914215112
     i: 12, t: 1.00000007353429
        p: 3.14159257658487
     i: 14, t: 1.00000000459589
        p: 3.14159264877699
     i: 16, t: 1.00000000028724
        p: 3.14159265328899
     i: 18, t: 1.00000000001795
        p: 3.14159265357099
     i: 20, t: 1.00000000000112
        p: 3.14159265358862
     i: 22, t: 1.00000000000007
        p: 3.14159265358972
     i: 24, t: 1
        p: 3.14159265358979
    pi: 3.14159265358979
    


Regular Expressions (REs): A separate page gives material about Regular Expressions, with emphasis on Ruby's object-oriented form of REs: Regular Expressions.


Ruby classes:
Revision date: 2013-11-07. (Please use ISO 8601, the International Standard Date and Time Notation.)