Introduction to Ruby Ruby (http://www.ruby-lang.org/en/ ) is a reflective, dynamic, objectoriented, single-pass interpreted programming language. It also has some functional programming features such as blocks and closures. Ruby has recently gained a lot of notice due to the rising popularity of Ruby on Rails, a MVC based web development framework. Features of Ruby – Purely object oriented Dynamically typed Has blocks and closures Iterators are built in Provides multiple inheritance through mixins Uses “duck typing,” i.e., unbounded polymorphism Reflection and metaprogramming Perl-compatible regular-expression support at syntax level Built-in support for certain design patterns Mark-and-sweep garbage collection OS-independent threading Easy interface to C modules Portable—developed mostly on GNU/Linux, but works on many types of UNIX, Mac OS X, Windows 95/98/Me/NT/2000/XP, DOS, BeOS, OS/2, etc Ruby is said to follow the “Principle of Least Surprise.” Installation Download and installation instructions can be found at http://www.rubylang.org/en/downloads/ A one-click installer is available for Windows. History Ruby was created by Yukihiro “Matz” Matsumoto in 1993. It was first released in 1995. Matz named it after a colleague’s birthstone. Lecture 4 Object-Oriented Languages and Systems 1 Matz blended parts of his favorite languages (Perl, Smalltalk, Eiffel, Ada, and Lisp) to form a new language that balanced functional programming with imperative programming. In 2006, Ruby achieved mass acceptance. With active user groups formed in the world’s major cities and Ruby-related conferences filled to capacity. It is the 10th most popular language in 2007 according to the TIOBE programming language popularity index (http://www.tiobe.com/tpci.htm), a position it has maintained. Much of the growth is attributed to the popularity of software written in Ruby, particularly the Ruby on Rails web framework (http://www.rubyonrails.org/) Comparison with Java Ruby Java Interpreted Compiled to byte code Dynamically typed Statically typed Purely object oriented Distinction between primitives and object types Unbounded polymorphism Inheritance and Interfaces Multiple inheritance through mixins Interfaces Not present (can do the same thing in Syntactic regular-expression Java by creating a pattern with support java.util.regex, etc.) Syntactic support for hashes nil is an object no nullpointer exceptions CSC/ECE 517 Lecture Notes Hashes (HashMap, Hashtable) present in Collections; no special syntax null means no reference to object (http://onestepback.org/articles/10things/9everythingi sanobject4.html) © 2009 Edward F. Gehringer 2 Everything is a message Method invocations are compiled, not treated as messages at run time. Possible to capture calls to non-existent methods using method_missing Not possible All classes are open to extension Classes cannot be extended at run time Dynamic evaluation of code using eval Not possible easily Reflection is easy Reflection is much more verbose (use methods defined in class Class) Blocks are closures Anonymous inner functions are closures but less powerful (This list is taken from http://jimweirich.tadalist.com/lists/public/14055) Basics Ruby code can be directly evaluated in the Interactive Ruby Browser. The results are immediately returned in the IRB. Ruby code is saved in files with the extension .rb and can be run by invoking the command ‘ruby filename.rb’ The files can be made executable on UNIX systems (without typing ruby) by adding the shebang line as the first line in the file #!/usr/bin/ruby The “Hello, world” program can be written as puts “Hello, world!” >>Hello, world! Functions are defined by the keyword def. The keyword end is to the closing parenthesis in other languages. def hello(i = 3) Lecture 4 Object-Oriented Languages and Systems 3 i.times do puts "Hello" end end hello >>Hello >>Hello >>Hello The arguments to a function can be initialized. Parentheses in a function call are optional So we can write hello(4) or hello 4. Both mean the same. Function arguments can be aggregated – def hi(*names) puts "Hello #{names.join(', ')}" end hi('Adam', 'Ben', 'Cathy') >>Hello Adam, Ben, Cathy The last statement in the function is the return value. There is no need for an explicit return. And all statements return a value. Dynamic Typing Variables in Ruby are not given a type. Their “type” depends upon the value assigned to them. PI = 3.14159 #constants are named in uppercase by convention a = 3 b = 4 c = [1,2,3,4] #Array Comments in Ruby start with # and run to the end of the line. CSC/ECE 517 Lecture Notes © 2009 Edward F. Gehringer 4 Dynamic typing makes many features possible, such as unbounded polymorphism and blocks, which are not available in statically typed languages such as Java and C++. Everything is an object Ruby is purely object-oriented. Everything in Ruby is an object. Even numbers and strings are objects. 1.succ >>2 "hi".upcase >>HI [3,1,4,2].sort >>[1,2,3,4] “Hi”.class >>String -------------------------------------------------Arrays An array in Ruby is an ordered list of elements. Simple examples illustrate the use of arrays: a = [] # empty array b = [1, 2, 3] # array of FixNums c = ['Hi', 'Hello'] #Array of strings Special syntax to create array of strings without the quotes: d = %w{Hi Hello} Appending elements to an Array – b << 4 >>[1, 2, 3, 4] Array indexes start from zero. Negative indexes count backward. So b[-1] refers to the last element of b. Arrays can also be created using the new keyword. new is used to create objects of any type. More about new will follow later. Lecture 4 Object-Oriented Languages and Systems 5 e = Array.new e << 7 puts e >> 7 Hashes Ruby has syntax support for hashes. What’s a “hash”? Any ideas? An “array” that can be indexed by arbitrary keys. Hashes can be created in three ways. Using the keyword new, or special syntax using Hash[] or {} capitals = { "USA" => "Washington DC", "India" => "New Delhi", "China" => "Beijing" } puts capitals["USA"] >>Washington DC numbers = Hash['one', 1, 'two', 2] puts numbers['two'] >>2 h = Hash.new h["a"] = 'b' puts h['a'] >>b Blocks Blocks are anonymous regions of code that can be associated with a method call. In Ruby, essentially blocks are closures, i.e., they can be passed around as values, executed on demand by “anyone” who has the value, CSC/ECE 517 Lecture Notes © 2009 Edward F. Gehringer 6 at which time, they can refer to variables accessible in the context where they were created. Blocks can be stored in variables and invoked when required. This powerful feature allows one to modify the behavior of a method at run time, and hence is an alternative to polymorphism. Similar functionality in other languages: C's function pointers C++'s function objects Python's lambdas and list comprehensions Perl's anonymous functions Java's anonymous inner classes These features live mostly in the corners of those languages, shunned by novice programmers. But closures are basic to Ruby. Here is a simple example – def mymethod puts "In the method" yield #Method passed control to block using yield puts "Back to the method" end mymethod { puts "Inside the block" } This outputs -- >>In the method >>Inside the block >>Back to the method [1,2,3].each { |i| puts i} >>1 >>2 >>3 Blocks can also be parametrized – def para_block yield("Dick") end Lecture 4 Object-Oriented Languages and Systems 7 para_block {|name| puts "Hello #{name}"} The output: >>Hello Dick Iterators An iterator is an object that is used to traverse over a collection such as an array. In Ruby, the iterator is internal: it is a method of the collection which can call a block of code. In other programming languages the iterator is an external object that maintains its state and is used to iterate over the collection. Using blocks, iterators can be implemented more concisely and clearly. class Fixnum def repeat for i in 1..self.to_i yield end end end Now, call the method with a block: 3.repeat {puts "Hello"} >>Hello >>Hello >>Hello Closures A closure is a block of code that “closes over”; that is, can access the lexical environment of its definition. # Class to generate adders class AdderGen def initialize(n) @block = lambda {|a| n + a} end CSC/ECE 517 Lecture Notes © 2009 Edward F. Gehringer 8 def add(a) @block.call a end end twoAdder = AdderGen.new 2 incrementer = AdderGen.new 1 puts incrementer.add(4) puts twoAdder.add(6) >>5 >>8 Here the instance variable @block is a closure. It remembers the parameter with which the initialize method was called even after the initialize method exits. This value is used when the block is called in the add method. lambda comes from lambda calculus in functional programming. It is used to generate new functions dynamically. Uses of closures: Write a program that prints out the contents of a file line by line, and insures that the file is always closed. Let’s add to the built-in file class. class File def File.open_and_process(*args) f = File.open(*args) yield f f.close() end end File.open_and_process("testfile", "r") do |file| while line = file.gets puts line end end This is line one This is line two Lecture 4 Object-Oriented Languages and Systems 9 This is line three And so on... Currying Currying means creating a new function out of an existing function by fixing the value of some of its input parameters. A simple example illustrates the concept. Consider a function which raises its first parameter to the power specified by the second parameter. def power(x,y) val = 1 1.upto(y) {|a| val = val * x} val end Now, one might want to define a function to square or cube a number. Instead of defining a new method, the existing one can be curried. In Ruby, we can curry a method using lambda. This is how the square and cube functions are created: square = lambda {|x| power(x,2)} cube = lambda {|x| power(x,3)} puts square.call(4) puts cube.call(3) >>16 >>27 CSC/ECE 517 Lecture Notes © 2009 Edward F. Gehringer 10