710 likes | 858 Views
Ruby for Java Developers. Neal Ford Architect Thought Works www.nealford.com www.thoughtworks.com nford@thoughtworks.com Blog: memeagora.blogspot.com. Questions, Slides, and Samples. Please feel free to ask questions anytime The slides and samples will be available at www.nealford.com
E N D
Ruby for Java Developers Neal Ford Architect ThoughtWorks www.nealford.com www.thoughtworks.com nford@thoughtworks.com Blog: memeagora.blogspot.com
Questions, Slides, and Samples • Please feel free to ask questions anytime • The slides and samples will be available at www.nealford.com • I’ll show that address again at the end
What This Session Covers: • The Ruby Philosophy • Ruby syntax • Classes and objects in Ruby • Closures • Mixins • Networking • Threading • Test-first coding in Ruby • Ruby as a scripting language • Ruby as a web language
What is Ruby? • From www.ruby-lang.org: • Ruby is the interpreted scripting language for quick and easy object-oriented programming. • It has many features to process text files and to do system management tasks (as in Perl). • It is simple, straight-forward, extensible, and portable. • Ruby has: • Simple syntax, partially inspired by Eiffel and Ada • Exception handling features, like Java or Python • operators that are syntax sugar for the methods. You can redefine them easily
Features of Ruby • Ruby is a complete, full, pure object oriented language. • All data in Ruby is an object, in the sense of Smalltalk: no exceptions. • Example: In Ruby, the number 1 is an instance of class Fixnum. • Ruby's OO is carefully designed to be both complete and open for improvements. • Example: Ruby has the ability to add methods to a class, or even to an instance during runtime. A • An instance of one class *can* behave differently from other instances of the same class.
Features of Ruby • Ruby features single inheritance only, *on purpose*. • But Ruby knows the concept of modules (called Categories in Objective-C). • Modules are collections of methods. • Every class can import a module and so gets all its methods for free. • Ruby features true closures. • Not just unnamed function, but with present variable bindings. • Ruby features blocks in its syntax (code surrounded by '{' ... '}' or 'do' ... 'end'). • These blocks can be passed to methods, or converted into closures.
Features of Ruby • Ruby features a true mark-and-sweep garbage collector. • It works with all Ruby objects. • You don't have to care about maintaining reference counts in extension libraries. • Integers in Ruby can (and should) be used without counting their internal representation. • There *are* small integers (instances of class Fixnum) and large integers (Bignum), but you need not worry over which one is used currently. • If a value is small enough, an integer is a Fixnum, otherwise it is a Bignum. Conversion occurs automatically.
Features of Ruby • Ruby needs no variable declarations. • It uses simple naming conventions to denote the scope of variables. • Ruby can load extension libraries dynamically if an OS allows. • Ruby features OS independent threading • Ruby is highly portable: it is developed mostly on Linux, but works on many types of UNIX, DOS, Windows 95/98/Me/NT/2000/XP, MacOS, BeOS, OS/2, etc. var_name => local variable @var_name => instance variable @var_name => global variable
Obtaining Ruby • The main Ruby site: www.ruby-lang.org • For Windows, the One-Click Ruby Installer project on RubyForge: http://rubyinstaller.rubyforge.org/wiki/wiki.pl • If you have Cygwin, you can choose Ruby as one of the language options • There is a specific cygwin-ruby version, optimized for Cygwin • The English reference manual is very outdated • However, http://www.ruby-doc.org/ has up to date RubyDocs and other resources • The Pick-Axe book (shown at the end)
Classes and Objects in Ruby class Employee def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end def to_s "Name is #{@name}, salary is #{@salary}, “ + "hire year is #{@hire_year}" end def raise_salary_by(perc) @salary += (@salary * 0.10) end end
Inheritance class Manager < Employee def initialize(name, salary, hire_year, asst) super(name, salary, hire_year) @asst = asst end def to_s super + ",\tAssistant info: #{@asst}" end def raise_salary_by(perc) perc += 2005 - @hire_year super(perc) end end
Tying It Together def show(emps) emps.each { |e| puts e } end employees = Array.new employees[0] = Employee.new("Homer", 200.0, 1995) employees[1] = Employee.new("Lenny", 150.0, 2000) employees[2] = Employee.new("Carl", 250.0, 1999) employees[3] = Manager.new("Monty", 3000.0, 1950, employees[2]) show(employees) employees.each { |e| e.raise_salary_by(10) } puts "\nGive everyone a raise\n\n" show employees
Class Members class Employee @@num_employees = 0; def Employee.num_employees @@num_employees end def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year @@num_employees += 1 end # . . . show employees puts "Number of employees in the company: " + Employee.num_employees.to_s
Properties class Employee def name @name end def salary @salary end def hire_year @hire_year end # . . . emp = Employee.new("Homer", 100.0, 2004) printf "Name: %s\n", emp.name printf "Salary: %d\n", emp.salary printf "Hire Year: %s\n", emp.hire_year
attr_reader class Employee attr_reader :name, :salary, :hire_year def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end # . . . emp = Employee.new("Homer", 100.0, 2004) printf "Name: %s\n", emp.name printf "Salary: %d\n", emp.salary printf "Hire Year: %s\n", emp.hire_year
Writable Properties class Employee def name @name end def name=(new_name) @name = new_name end def salary @salary end def hire_year @hire_year end # . . . emp = Employee.new("Homer", 100.0, 2004) emp.name = "Monty" printf "Name: %s\n", emp.name
attr_writer class Employee attr_reader :name, :salary, :hire_year attr_writer :name, :salary, :hire_year def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end #. . . emp = Employee.new("Homer", 100.0, 2004) emp.name = "Monty" printf "Name: %s\n", emp.name
Singletons class Logger private_class_method :new @@logger = nil def Logger.create @@logger = new unless @@logger @@logger end end puts Logger.create.object_id puts Logger.create.object_id
Access Control • Ruby has 3 levels of access control
Private vs. Protected • The difference is subtle • Protected • Can be called by any instance of the defining class or its subclasses • Private • Called only within the context of the calling object • Never possible to access another object’s private methods directly, even if the object is the same class as the caller
Arrays - General Syntax a = [ 3.14159, "pi", 99 ] a.type » Array a.length » 3 a[0] » 3.14159 a[1] » "pi" a[2] » 99 a[3] » nil b = Array.new b.type » Array b.length » 0 b[0] = "second" b[1] = "array" b » ["second", "array"]
Arrays - Indexing • Negative indices a = [ 1, 3, 5, 7, 9 ] a[-1] » 9 a[-2] » 7 a[-99] » nil • Pairs a = [ 1, 3, 5, 7, 9 ] a[1, 3] » [3, 5, 7] a[3, 1] » [7] a[-3, 2] » [5, 7]
Arrays - Ranges a = [ 1, 3, 5, 7, 9 ] a[1..3] » [3, 5, 7] a[1...3] » [3, 5] a[3..3] » [7] a[-3..-1] » [5, 7, 9]
Array Assignment a = [ 1, 3, 5, 7, 9 ] » [1, 3, 5, 7, 9] a[1] = 'foo' » [1, "foo", 5, 7, 9] a[-3] = 'bar' » [1, "foo", "bar", 7, 9] a[3] = [ 9, 8 ] » [1, "foo", "bar", [9, 8], 9] a[6] = 99 » [1, "foo", "bar", [9, 8], 9, nil, 99]
Array Assignment Ranges a = [ 1, 3, 5, 7, 9 ] » [1, 3, 5, 7, 9] a[2, 2] = 'cat' » [1, 3, "cat", 9] a[2, 0] = 'dog' » [1, 3, "dog", "cat", 9] a[1, 1] = [ 9, 8, 7 ] » [1, 9, 8, 7, "dog", "cat", 9] a[0..3] = [] » ["dog", "cat", 9] a[5] = 99 » ["dog", "cat", 9, nil, nil, 99]
Hashes h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' } h.length » 3 h['dog'] » "canine" h['cow'] = 'bovine' h[12] = 'dodecine' h['cat'] = 99 h » {"cow"=>"bovine", "cat"=>99, 12=>"dodecine", "donkey"=>"asinine", "dog"=>"canine"}
Blocks class EmployeeList def initialize @employees = Array.new end def add(an_employee) @employees.push(an_employee) self end def delete_first @employees.shift end def delete_last @employees.pop end
Blocks def show @employees.each { |e| puts e } end def [](key) if key.kind_of?(Integer) @employees[key] end end end list = EmployeeList.new list.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999)) list.show puts "Employee #1 is " + list[0].to_s
Iterating with for def [](key) if key.kind_of?(Integer) @employees[key] else for i in 0..@employees.length return @employees[i] if key == @employees[i].name end end return nil end end list = EmployeeList.new list.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999)) list.show puts "Employee #1 is " + list[0].to_s puts "Employee named 'Homer' is " + list["Homer"].to_s
Using Blocks def [](key) if key.kind_of?(Integer) result = @employees[key] else result = @employees.find{ |anEmp| key == anEmp.name } end return result end end puts "Employee #1 is " + list[0].to_s puts "Employee named 'Homer' is " + list["Homer"].to_s
The Ruby Way def [](key) return @employees[key] if key.kind_of?(Integer) return @employees.find { |anEmp| key == anEmp.name } end end list = EmployeeList.new list.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999)) list.show puts "Employee #1 is " + list[0].to_s puts "Employee named 'Homer' is " + list["Homer"].to_s
Building Iterators def fib_up_to(max) i1, i2 = 1, 1 # parallel assignment while i1 <= max yield i1 i1, i2 = i2, i1+i2 end end fib_up_to(1000) { |f| print f, " " }
Blocks for Transactions 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 |aFile| print while aFile.gets end
Blocks for Transactions, Take 2 class File def File.myOpen(*args) aFile = File.new(*args) # If there's a block, pass in the file and close # the file when it returns if block_given? yield aFile aFile.close aFile = nil end return aFile end end
Closures • Closures represent a (nameless) block of code that retains its context, even if the context is out of scope • A closure object has code to run, the executable, and state around the code, the scope. • You can refer to the local variables inside a closure. • Even after the function has returned, and its local scope has been destroyed, the local variables remain in existence as part of the closure object. • When no one refers to the closure anymore, it's garbage collected, and the local variables go away.
Closure Example def make_counter var = 0 proc do # coverts a block to a proc object var +=1 end end c1 = make_counter c1.call c1.call c1.call c2 = make_counter puts "c1 = #{c1.call}, c2 = #{c2.call}" # output: c1 = 4, c2 = 1
Regular Expressions • Regular expressions are a built-in data type in Ruby a = Regexp.new('^\s*[a-z]') » /^\s*[a-z]/ b = /^\s*[a-z]/ »/^\s*[a-z]/ c = %r{^\s*[a-z]} » /^\s*[a-z]/ a = "Fats Waller" a =~ /a/ » 1 a =~ /z/ » nil a =~ "ll" » 7
Extending Classes • Classes are never closed in Ruby • You can add new methods to user or system classes • Just open the definition and start typing! class String def rot(num = 13) return self.split("").collect { |ch| if /^[a-z]$/ === ch ((ch[0] + num - 'a'[0]) % 26 + 'a'[0]).chr elsif /^[A-Z]$/ === ch ((ch[0] + num - 'A'[0]) % 26 + 'A'[0]).chr else ch end }.join("") end alias rot13 rot end
Conditionals • True in Ruby implies • Any value that is not nil or the constant false • Zero is not false • A zero-length string is not false • Ruby includes an operator defined? • Returns nil if its argument (which can be an arbitrary expression) is not defined • Otherwise returns a description of the argument defined? 1 » "expression" defined? dummy » nil defined? printf » "method" defined? String » "constant"
if Expression • General form: if emp.name == "Homer" then handle = "slacker" elsif emp.name == "Lenny" then handle = "go-getter" else handle = "unknown" end
if Expression • If you lay them out on separate lines, you can skip the then keyword if emp.name == "Homer" handle = "slacker" elsif emp.name == "Lenny" handle = "go-getter" else handle = "unknown" end
if Expression • You can use if as an expression if you want handle = if emp.name == "Homer" then "slacker" elsif emp.name == "Lenny" then "go-getter" else "unknown" end
unless, Conditional Expressions • The unless expression is a negated if statement unless emp.salary > 2000 then raise_percentage = 0.10 else raise_percentage = 0.11 end • There is also the ternary operator raise_percentage = empl.salary > 2000 ? 0.10 : 0.11
If and Unless Modifiers • Like Perl, statement modifiers allow you to attach conditional statements onto the end of a normal statement mon,day,year = $1,$2,$3 if /(\d\d)-(\d\d)-(\d\d)/ puts "a = #{a}" if debug print total unless total == 0 while gets next if /^#/ # Skip comments parseLine unless /^$/ # Don't parse empty lines end
Case Expressions case inputLine when "debug" dumpDebugInfo dumpSymbols when /p\s+(\w+)/ dumpVariable($1) when "quit", "exit" exit else print "Illegal command: #{inputLine}" end
Case + Regular Expressions case line when /name=(.*)/ puts "Name is #$1" when /salary=(.*)/ puts "Salary is #$1" when /Hire\sYear=(.*)/ puts "Hire Year is #$1" end
Loops • While loop while gets # . . . end • Until loop until emp.salary > 2500 employee_list.add(emp.pop) end
Loops as Modifiers a *= 2 while a < 100 a -= 10 until a < 100
Iterators 3.times do print "knock " end # knock knock knock 0.upto(9) do |x| print x, " " end #0 1 2 3 4 5 6 7 8 9 0.step(12, 3) {|x| print x, " " } # 0, 3, 6, 9, 12