JMU CS 430 Spring 2021

Programming Languages

Module 01 Lab

  1. Open a terminal window. Start up the interactive Ruby interpreter by typing irb.

  2. Do some arithmetic, like 4+37 or 8*23. Ruby is a very nice calculator. Note that you can use the arrow keys to move around and see expressions that you typed before. The variable _ (that’s a single underscore) always holds the value of the last expression evaluated. You can also assign the results of expressions to variables using the assignment operator = (as in Java).

  3. Besides printing the result of an expression when you type it, you can print several expressions using the print method (which does not print newlines after the expressions it prints) and the puts method (which does). Type print 4, 5, 2**100 and then puts 4, 5, 2**100 to see the differences. (What is 2**100?) Notice we are able to compute enormous numbers. Ruby has plain and big integer types, and big integers can be arbitrarily large. It switches between them automatically as it needs to.

  4. Ruby has large libraries of operations. For example, there are many operations in the Math module. Type puts Math.sin(3), Math.log2(2**100) (Now do you know what 2**100 is?)

  5. All values in Ruby are instances of classes, that is, objects. This is what is meant by saying that Ruby is a pure object-oriented language. Numeric classes have many methods built into them besides the usual operations (addition, subtraction, etc.). For example, test whether some integer is even by using the even? method (for example, try 5.even?). Now try that method on a floating point number. Find the absolute value (abs) of a floating point number. Find the greatest common divisor (gcd) of 15742 and 646 (make one of the numbers the host of the operation and the other the argument to the gcd method). Many of the operations found in a math library in other languages are built in to the numeric classes in Ruby.

  6. Ruby has powerful string processing capabilities. Assign a long string to a variable, such as s = 'Two roads diverged in a yellow wood'. To get an idea of some of the things Ruby can do with strings, type the following and figure out what is going on:

    s[0]
    s[0..8]
    s[4,5]
    s[-1]
    s[-11..-1]
    s[0..-1]
    s[1000]
    s.reverse
    s*2
    s.include?('in')
    s.next
    s.split
    s.tr(' ','+')
    
  7. We could do more with irb, including typing in whole programs, but that is inconvenient, so lets switch to another environment. Type quit to exit, and change directories (or create a directory) to a place you can put some files.

  8. Open some text editor and create a new file called lab1.rb. I recommend vi because it is universally available and works for any text file, and is very fast to use once you learn it. You might also try nano. There is an Eclipse plugin for Ruby as well if you want to install that.

  9. Type puts "Hello world" in a line of this file and save it. This is a complete Ruby hello program.

  10. Open another window in the same directory and type ruby lab1.rb. Note that irb is the interactive version of the interpreter, and ruby is the version of the interpreter that executes scripts (programs). All Ruby scripts have the extension .rb.

  11. Ruby comments start with # and extend to the end of the line. Make a comment at the start of your script and put your name in it.

  12. Ruby has most of the usual control structures, including if-then, if then-else, if-then- elsif, and case (switch) statements, and while, until, and for loops, and break, continue, and return statements, but they often work a little differently from what you may be used to in Java.

  13. To start with, conditional expressions do not need to be enclosed in parentheses. Also, the statements in the body of a control structure are not enclosed in curly brackets, and statements can be (and should be) separated by newlines rather than semicolons. Type the following in the editor window after the hello line (notice that we usually indent 2 spaces in Ruby):

    x = -5
    if x < 0
      puts "negative"
    else
      puts "positive"
    end
    

    Save the file and switch focus to the execution window. Run the script (hint: use the arrow keys to navigate to what you typed before).

  14. Add code to the script to count down from 10 to 1 and print these numbers using a while loop. You should be able to figure out how to do this.

  15. In Java you can write a short conditional on a single line if you like, with the statements separated by semicolons, but in Ruby, newlines are conventionally used to separate statements, so this is considered ugly. For example, you could write

    if x < 0; puts "negative"; end
    

    or

    if x < 0 then puts "negative" end
    

    on one line, but this is frowned on. However, Ruby allows some control structures to be used as statement modifiers, so we can just write

    puts "negative" if x < 0
    

    on a single line instead (notice the lack of semi-colons and other keywords). You can also use unless (which means if-not), while, and until as statement modifiers. This is encouraged because it shortens code.

  16. In your loop that counts down from 10 to 0, add a conditional modifier (on a single line) so that the number is printed only if it is even (remember the even? method?).

  17. You probably used a statement like k = k - 1 in your while loop to decrement the loop control variable. In Java, you would probably have written k--, but Ruby does not have increment and decrement operators. However, Ruby (like Java) does have a host of compound assignment operators, so that decrement operation can be written k -= 1. It is conventional in Ruby to use these operators, so change your loop to use one.

  18. As mentioned, Ruby has a case control structure. It is treated like a series of conditionals, so that the cases are tested in order and the first one that is true is executed. To illustrate, add the following to your script:

    r = rand(100)
    case
    when r.even?
      print r, " is even\n"
    when r < 10
      print r, " is small\n"
    else
      print r, " is big and odd\n"
    end
    

    The rand(n) method generates a random integer between 0 and n-1. There are other forms of the case statement, but this illustrates its basic use. Save your script and run it again.

  19. By the way, if Ruby is a pure object-oriented language, then every operation should be a method. If so, then how can we have operations like print and rand that don’t seem to be attached to any object (in Ruby-speak, they don’t seem to have a receiver)? The explanation is that there is a built-in variable called self (the same as this in Java) that always has the current object as its value. When the interpreter starts up, this value is set to main, an instance of the Object class. Type puts self, self.class and run your script to verify this.

  20. When a method is called without an explicit receiver, self is used as the receiver. So when print and rand are called in our script, the receiver is self, and this works because print and rand are methods of the Object class (they are actually in the Kernel module, which is included in the Object class).

  21. Fire up a browser and go to ruby-doc.org. This is the best online source for documentation about Ruby. Search for the Kernel module. Find rand and read its description.

  22. Lets switch back to irb for a while; you can just open another window and start it up.

  23. Ruby has a built-in Array class. Arrays are indexed the same way that strings are, so we know how to do that already. We can make arrays with array literals, which list the elements in an array between square brackets. Make the array ['cat', 'on', 'mat'] and assign it to the variable a.

  24. Lets replace some elements. Ruby allows the same syntax for replaced elements as it does for result elements; in other words, a[1,2] (two elements starting at 1) can be a value or it can designate elements to be replaced. Type the following command: a[1,2] = 'is', 'on', 'the', 'mat' and then examine the contents of a.

  25. You can insert between by indicating the index of the insertion point and that 0 elements are to be replaced. For example, a[0,0] = 'the' inserts 'the' at the start of the list. Try this.

  26. There are other ways to make arrays: for example, you can also make arrays using the classname and the new operator. Array.new creates a new empty array (whose array literal is [] by the way); Array.new(4) creates an array with four nil values (nil is like a null pointer in Java); Array.new(4, true) creates an array of four boolean values.

  27. Ruby has lots of operations for manipulating arrays. For example, type the following into irb and see what you get:

    a = Array.new(3, "hello")
    a = a + [1, 2, 3]
    a << true
    a.join(' ')
    a.shuffle
    
  28. Unlike most other languages, Ruby allows different typed values in arrays, as indicated by the last example.

  29. An Array is an Enumerable, which means that Array implements the operations of the Enumerable module. These operations are mostly iterators that invoke a block of code repeatedly. For example, if a is an array then a.each invokes a block of code once for every element of array a. Blocks are specified between curly braces or the keywords do and end. Conventionally, blocks that fit on one line use curly braces and those that don’t use do and end. Type the following into irb and see what happens:

    a.each { puts 'hi ' }
    
  30. You should have seen “hi” printed seven times, which is how many elements there are in array a. This is not very interesting, but we can pass parameters to blocks. Parameters are listed between | characters at the start of a block. So now try this:

    a.each { | w | puts "hi #{w} " }
    

    (Notice the string interpolation expression here: #{w} in the string is replaced with the value of w.)

  31. Note how much easier this is than making a loop on the index of the array. We can still get the index if we want it. Use the iterator each_with_index to print the words and their indices in array a (the index will be passed as the second block parameter).

  32. Iterators are extremely important in Ruby. They are the usual way to make loops. Furthermore, you can write your own iterators: any method can be passed a block of code, and the special yield keyword passes control, along with any parameters, to the block.

  33. Now lets consider another kind of collection in Ruby. A Hash is a map or dictionary that associates (maps) a key to a value. Each key is mapped to only a single value, but several keys can be mapped to the same value. The keys and the values can be any types.

  34. Type the following:

    h = {}
    h[2] = "two"
    h[true] = "true"
    h
    h[2]
    h["true"]
    

    The {} is the literal for an empty Hash. The two assignments map the keys 2 and true to string values. Note that a value can be retrieved by its key, but a key cannot be retrieved by its value. Even though Hash syntax is similar to Array syntax, Array indices must be integers while Hash keys can be anything, and Array elements are in order while Hash elements are not.

  35. Lets switch back to the file lab1.rb again.

  36. Methods in Ruby are created using the def/end keywords. Parameters are indicated between parentheses, and return values are indicated with the keyword return. For example, the following method returns true if an object has size of five or more.

    def has5?(a)
        return 5 <= a.size
    end
    

    Notice that because Ruby is dynamically typed, this will work on anything with a size method.

  37. In your lab file write a method genArray(n) to create and return an array with the numbers 1 to n in it. Add the line print genArray(10), "\n" after your function definition. Run your program.

  38. Ruby methods can have default parameters, which means that if you leave out the parameter, it will still have a value in the method. These are specified like assignment statements, for example def f(x=5) ... makes 5 the default value of parameter x. Make the default value of n 10 in genArray and add the line print genArray(), "\n" to your file. Run the program.

  39. Ruby is quite cavalier about arguments: you don’t need to enclose them in parentheses (but not doing so makes your code harder to read). This also means that if a method has no arguments, then the parentheses can be omitted. This makes your code easier to read. Add the line print genArray, "\n" to your file. Run the program.

  40. Ruby supports recursion. To finish up, rewrite genArray so it uses recursion rather than a loop, an iterator, or a range, calling the new method genArr. Test it.

This lab was originally written by Dr. Chris Fox.