Ruby for Java Developers
description
Transcript of Ruby for Java Developers
Ruby for Java Developers
Neal FordArchitect
ThoughtWorkswww.nealford.com
Blog: memeagora.blogspot.com
2
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
3
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
4
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
5
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.
6
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.
7
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.
8
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
9
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)
10
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) endend
11
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) endend
12
Tying It Together
def show(emps) emps.each { |e| puts e }end
employees = Array.newemployees[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
13
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
14
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.nameprintf "Salary: %d\n", emp.salaryprintf "Hire Year: %s\n", emp.hire_year
15
attr_readerclass 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.nameprintf "Salary: %d\n", emp.salaryprintf "Hire Year: %s\n", emp.hire_year
16
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
17
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
18
Singletons
class Logger private_class_method :new @@logger = nil def Logger.create @@logger = new unless @@logger @@logger endend
puts Logger.create.object_idputs Logger.create.object_id
19
Access Control
• Ruby has 3 levels of access control
public • Called by anyone (i.e., no access control)• Default access (except for initialize, which is always private
protected • Only by objects of the defining class and its subclasses
private • Cannot be called without an explicit receiver• You cannot specify an object when using them• Can be called only in the defining class and by direct descendents within that same object
20
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
21
Arrays - General Syntax
a = [ 3.14159, "pi", 99 ]a.type » Arraya.length » 3a[0] » 3.14159a[1] » "pi"a[2] » 99a[3] » nilb = Array.newb.type » Arrayb.length » 0b[0] = "second"b[1] = "array"b » ["second", "array"]
22
Arrays - Indexing
• Negative indicesa = [ 1, 3, 5, 7, 9 ]a[-1] » 9a[-2] » 7a[-99] » nil
• Pairsa = [ 1, 3, 5, 7, 9 ]a[1, 3] » [3, 5, 7]a[3, 1] » [7]a[-3, 2] » [5, 7]
23
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]
24
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]
25
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]
26
Hashes
h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
h.length » 3h['dog'] » "canine"h['cow'] = 'bovine'h[12] = 'dodecine'h['cat'] = 99h » {"cow"=>"bovine",
"cat"=>99, 12=>"dodecine", "donkey"=>"asinine", "dog"=>"canine"}
27
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
28
Blocks def show @employees.each { |e| puts e } end def [](key) if key.kind_of?(Integer) @employees[key] end endend
list = EmployeeList.newlist.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999))
list.showputs "Employee #1 is " + list[0].to_s
29
Iterating with for def [](key) if key.kind_of?(Integer) @employees[key] else for i in [email protected] return @employees[i] if key == @employees[i].name end end return nil endend
list = EmployeeList.newlist.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999))
list.showputs "Employee #1 is " + list[0].to_sputs "Employee named 'Homer' is " + list["Homer"].to_s
30
Using Blocks
def [](key) if key.kind_of?(Integer) result = @employees[key] else result = @employees.find{ |anEmp| key == anEmp.name } end return result endend
puts "Employee #1 is " + list[0].to_sputs "Employee named 'Homer' is " +
list["Homer"].to_s
31
The Ruby Way
def [](key) return @employees[key] if key.kind_of?(Integer) return @employees.find { |anEmp| key == anEmp.name } endend
list = EmployeeList.newlist.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999))
list.showputs "Employee #1 is " + list[0].to_sputs "Employee named 'Homer' is " +
list["Homer"].to_s
32
Building Iterators
def fib_up_to(max) i1, i2 = 1, 1 # parallel assignment while i1 <= max yield i1 i1, i2 = i2, i1+i2 endend
fib_up_to(1000) { |f| print f, " " }
33
Blocks for Transactions
class File def File.open_and_process(*args) f = File.open(*args) yield f f.close() endend
File.open_and_process("testfile", "r") do |aFile| print while aFile.getsend
34
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 endend
35
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.
36
Closure Exampledef make_counter var = 0 proc do # coverts a block to a proc object
var +=1 endend
c1 = make_counterc1.callc1.callc1.call
c2 = make_counter
puts "c1 = #{c1.call}, c2 = #{c2.call}"# output: c1 = 4, c2 = 1
37
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/ » 1a =~ /z/ » nila =~ "ll" » 7
38
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 rotend
39
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 » nildefined? printf » "method"defined? String » "constant"
40
Comparison Operators
Operator Meaning
== Equality
=== Equality in a case statement when clause
<=> General comparison operator, returns -1, 0, +1
<, <=, >=, >
Standard comparisons
=~ Regular expression pattern match
eql? True if receiver and argument are both the same type and have equal values1 == 1.0 >> true 1.eql?(1.0) >> false
equal? True if the receiver and argument have the same object id
41
if Expression
• General form:
if emp.name == "Homer" then handle = "slacker"elsif emp.name == "Lenny" then handle = "go-getter"else handle = "unknown"end
42
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
43
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
44
unless, Conditional Expressions
• The unless expression is a negated if statement
unless emp.salary > 2000 then raise_percentage = 0.10else raise_percentage = 0.11end
• There is also the ternary operator
raise_percentage = empl.salary > 2000 ? 0.10 : 0.11
45
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 debugprint total unless total == 0
while gets next if /^#/ # Skip comments parseLine unless /^$/ # Don't parse empty lines
end
46
Case Expressions
case inputLine when "debug" dumpDebugInfo dumpSymbols when /p\s+(\w+)/ dumpVariable($1) when "quit", "exit" exit else print "Illegal command: #{inputLine}"end
47
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
48
Loops
• While loop
while gets # . . .end
• Until loop
until emp.salary > 2500 employee_list.add(emp.pop)end
49
Loops as Modifiers
a *= 2 while a < 100
a -= 10 until a < 100
50
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
51
Exceptions
the_file = File.open(name, "w")begin # Exceptions raised by this code will # be caught by the following rescue clause while data = socket.read(512) the_file.write(data) end
rescue SystemCallError $stderr.print "IO failed: " + $! the_file.close File.delete(name) raiseend
52
ensure and else
f = File.open("testfile")begin # .. processrescue # .. handle errorelse puts "Congratulations-- no errors!"ensure f.close unless f.nil?end
53
retrybegin if @esmtp then @command.ehlo(helodom) else @command.helo(helodom) end
rescue ProtocolError if @esmtp then @esmtp = false retry else raise endend
54
Modules
• Modules allow you to group methods, classes, and constants• Provide unique namespaces• Implement mixins
• Namespaces allow you to partition your application into separate files
• Use the require keyword to reference other modules
55
Mixins
• Ruby does not support multiple inheritance• Instead, it supports mixins• You define methods within a module• You include that module into a class
56
Mixins
module Debug def who_am_i? "#{self.class.name} " + "(\##{self.object_id}): #{self.to_s}" endend
class Employee include Debug . . .endclass Manager < Employee include Debug . . .end
puts "\n\nWho are they?"puts employees[0].who_am_i?puts employees[3].who_am_i?
57
Threadsrequire 'net/http'
pages = %w( www.rubycentral.com www.nealford.com www.pragmaticprogrammer.com )threads = []for page in pages threads << Thread.new(page) { |myPage| h = Net::HTTP.new(myPage, 80) puts "Fetching: #{myPage}" resp, data = h.get('/', nil ) puts "Got #{myPage}: #{resp.message}" }end
threads.each { |aThread| aThread.join }
58
Thread Variables
• Thread has a special facility that allows thread-local variables to be created and accessed by name
• Treat the thread object as if it were a Hashcount = 0arr = []10.times do |i| arr[i] = Thread.new { sleep(rand(0)/10.0) Thread.current["mycount"] = count count += 1 }endarr.each {|t| t.join; print t["mycount"], ", " }puts "count = #{count}"
59
Testing and Rubyrequire 'test/unit/testcase'require 'Employee'
class TestEmployee < Test::Unit::TestCase def setup @emp = Employee.new("Homer", 3000, 2004) end def teardown @emp = nil end
def test_raise_salary_by @emp.salary = 2000 perc = 10; expected_salary = @emp.salary +
(@emp.salary * 0.1 * perc) @emp.raise_salary_by(10) assert(@emp.salary = expected_salary) endend
60
Testing and Ruby
class TestEmployeeList < Test::Unit::TestCase
def setup @list = EmployeeList.new @test_subject =
Employee.new("Homer", 3000, 2004) end def teardown @list = nil @test_subject = nil end def test_add @list.add(@test_subject) assert(@list.delete_last.eql?(@test_subject)) assert(@list.empty?) end
61
Testing and Rubyrequire 'test/unit/testsuite'require 'test/unit/ui/tk/testrunner'require 'test/unit/ui/console/testrunner'require 'TestEmployee'require 'TestEmployeeList'
class TestSuite_AllTests def self.suite suite = Test::Unit::TestSuite.new("HR Tests") suite << TestEmployee.suite suite << TestEmployeeList.suite return suite endend
#Test::Unit::UI::Tk::TestRunner.run(TestSuite_AllTests)Test::Unit::UI::Console::TestRunner.run(
TestSuite_AllTests)
62
Controlling Windows
require 'Win32API'require 'win32ole'
class DailyLogs private @@Home_Dir = "c:\\MyDocuments\\" def get_doc_list docs = Array.new docs << "im\\2005 Weekly Workout Planner.xls" docs << "im\\2005 Workout Log.xls" docs << "trackers\\Master Task List.xls" end
63
Controlling Windows
public def open_daily_logs excel = WIN32OLE.new("excel.application") workbooks = excel.WorkBooks excel.Visible = true get_doc_list.each { |f| workbooks.Open(@@Home_Dir + f, true) } excel.Windows.Arrange(7) end end
DailyLogs.new.open_daily_logs
64
Java vs. Ruby: Java, City.javapublic class City { private String name; private String symposiumName; private int showId;
public City(final String name, final String symposiumName, final int showId) { this.name = name; this.symposiumName = symposiumName; this.showId = showId; }
public String getName() { return name; }
public String getSymposiumName() { return symposiumName; }
public int getShowId() { return showId; }}
65
Java vs. Ruby, Harvester.javapublic class Harvester { public static final String WEB_SITE = "http://www.nofluffjuststuff.com"; public static final String PAGE_NAME = "/show_topics.jsp?showId="; private static final String NAME_REGEX = "Neal\\sFord";
public Harvester() { int count; System.out.println("No Fluff, Just Stuff sessions:"); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); try { for (Iterator<City> cityItr = createCityList().iterator(); cityItr.hasNext();) { count = 0; City city = cityItr.next(); StringBuffer content = getWebContent(city); Matcher matcher = generateRegexForMatch(content); while (matcher.find()) { count++; } System.out.println(city.getName() + " = " + count); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
66
Java vs. Ruby, Harvester.java private Matcher generateRegexForMatch(final StringBuffer content) { Pattern pattern = Pattern.compile(NAME_REGEX); return pattern.matcher(content); }
private StringBuffer getWebContent(final City city) throws IOException {
URL url = new URL(WEB_SITE + PAGE_NAME + city.getShowId()); HttpURLConnection http = (HttpURLConnection) url.openConnection(); InputStream is = http.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line; StringBuffer content = new StringBuffer(1000); while ((line = reader.readLine()) != null) { content.append(line); } return content; }
private List<City> createCityList() { List<City> cityList = new ArrayList<City>(10); cityList.add(new City("Salt Lake", "Salt Lake", 16)); cityList.add(new City("Pacific Northwest", "Seattle", 32)); return cityList; }}
67
Java vs. Ruby, harvester.rbrequire 'net/http'
class City def initialize(symposium_name, city_name, show_id) @city_name = city_name @symposium_name = symposium_name @show_id = show_id end attr_reader :city_name, :symposium_name, :show_idend
def check_city(http, url, city) count = 0 http.request_get(url) { |response| response.read_body { |body| body.scan(/Neal\sFord/) { count += 1 } } } printf("%-20s = %d\n", city, count)end
68
Java vs. Ruby, harvester.rb
Page_Name = '/show_topics.jsp?showId='cities = Array.newcities << City.new('Salt Lake', 'Salt Lake', 16)cities << City.new('Pacific', 'Seatle', 32)
http = Net::HTTP.new('www.nofluffjuststuff.com', 80)
puts "No Fluff, Just Stuff sessions:"puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"cities.each { |city| check_city(http, Page_Name + city.show_id.to_s, city.city_name)}
69
Resources:
The “Pick-Axe” Book
Programming Ruby: The Pragmatic Programmer’s Guide
Dave Thomas, Andy Hunt
Ruby in a Nutshell
Yukihiro Matsumoto, David L. Reynolds (Translator)
70
Resources:
Ruby Developer's Guide
Michael Neumann, Jonothon Ortiz, Robert Feldt, Lyle Johnson
The Ruby Way
Hal Fulton, Guy Hurst